공분산과 역 분산의 차이


답변:


266

문제는 "공분산과 공분산의 차이점은 무엇입니까?"입니다.

공분산 및 반공 분산은 집합의 한 멤버를 다른 멤버와 연결하는 매핑 함수의 속성입니다 . 보다 구체적으로, 맵핑은 그 세트에 대한 관계 와 관련하여 공변량 또는 반 변형 일 수있다 .

모든 C # 유형 집합의 다음 두 가지 하위 집합을 고려하십시오. 먼저:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

둘째, 이것은 분명히 관련이 있습니다.

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }

사상 두 번째 세트 첫 세트에서 작업을. 즉, 첫 번째 세트의 각 T에 대해 두 번째 세트의 해당 유형은 IEnumerable<T>입니다. 또는 간단히 말해서 매핑은 T → IE<T>입니다. 이것은 "가는 화살표"입니다.

지금까지 나와 함께?

이제 관계를 고려해 봅시다 . 가 할당 호환성 관계 첫 번째 세트의 유형 쌍 사이는. type의 값은 type Tiger의 변수에 할당 될 수 Animal있으므로 이러한 유형은 "할당 지정"이라고합니다. 더 짧은 X형식으로 Y" 유형의 값을 유형 의 변수에 할당 할 수 있습니다 "라고 작성하겠습니다 X ⇒ Y. 이것은 "뚱뚱한 화살표"입니다.

첫 번째 하위 집합에는 다음과 같은 모든 할당 호환성 관계가 있습니다.

Tiger   Tiger
Tiger   Animal
Animal  Animal
Banana  Banana
Banana  Fruit
Fruit   Fruit

특정 인터페이스의 공변량 할당 호환성을 지원하는 C # 4에서는 두 번째 세트의 유형 쌍간에 할당 호환성 관계가 있습니다.

IE<Tiger>   IE<Tiger>
IE<Tiger>   IE<Animal>
IE<Animal>  IE<Animal>
IE<Banana>  IE<Banana>
IE<Banana>  IE<Fruit>
IE<Fruit>   IE<Fruit>

매핑 T → IE<T> 은 할당 호환성의 존재와 방향을 유지합니다 . 즉 X ⇒ Y, 그렇다면 IE<X> ⇒ IE<Y>.

뚱뚱한 화살표의 양쪽에 두 가지가 있으면 양쪽을 해당 얇은 화살표의 오른쪽에있는 것으로 바꿀 수 있습니다.

특정 관계에 대해이 속성을 갖는 매핑을 "공변량 매핑"이라고합니다. 이것은 일련의 동물이 필요한 곳에서 일련의 호랑이를 사용할 수 있지만 그 반대는 사실이 아닙니다. 일련의 호랑이가 필요한 곳에 일련의 동물을 반드시 사용할 수는 없습니다.

공분산입니다. 이제 모든 유형 세트의이 서브 세트를 고려하십시오.

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }

이제 첫 번째 세트에서 세 번째 세트로의 맵핑이 있습니다 T → IC<T>.

C # 4에서 :

IC<Tiger>   IC<Tiger>
IC<Animal>  IC<Tiger>     Backwards!
IC<Animal>  IC<Animal>
IC<Banana>  IC<Banana>
IC<Fruit>   IC<Banana>     Backwards!
IC<Fruit>   IC<Fruit>

즉, 매핑 T → IC<T>존재유지하지만 할당 호환성 방향반대로했습니다 . 즉 X ⇒ Y, 그렇다면 IC<X> ⇐ IC<Y>.

관계 를 유지하지만 반대로 하는 매핑을 반 변형 매핑 이라고합니다 .

다시 말하지만 이것은 분명해야합니다. 두 동물을 비교할 수있는 장치는 두 호랑이를 비교할 수 있지만 두 호랑이를 비교할 수있는 장치는 두 동물을 반드시 비교할 수는 없습니다.

이것이 C # 4의 공분산과 공분산의 차이입니다. 공분산 은 할당의 방향을 유지 합니다. 불균형은 반대로 됩니다.


4
나와 같은 사람에게는 공변량이 아닌 것과 반 변하지 않는 것과 둘 다가 아닌 것을 보여주는 예제를 추가하는 것이 좋을 것입니다.
bjan

2
@Bargitta : 매우 비슷합니다. 차이점은 C #은 명확한 사이트 분산 을 사용하고 Java는 호출 사이트 분산을 사용 한다는 입니다. 따라서 상황이 변하는 방식은 동일하지만 개발자가 "이것은 변형이 필요합니다"라고 말하는 방식이 다릅니다. 또한, 두 언어의 기능은 부분적으로 같은 사람에 의해 설계되었습니다!
Eric Lippert

2
@AshishNegi : "사용할 수있는"화살표를 읽습니다. "호랑이를 비교할 수있는 것으로 동물을 비교할 수있는 것이 사용될 수 있습니다." 지금 이해가 되나요?
Eric Lippert

1
@AshishNegi : 아니요, 그렇습니다. T는 IEnumerable 메소드의 리턴에만 표시되므로 IEnumerable은 공변량입니다. T는 IComparable 메소드의 형식 매개 변수로만 표시 되므로 IComparable은 반 변형 입니다.
Eric Lippert

2
@AshishNegi : 이러한 관계의 기본이 되는 논리적 이유 에 대해 생각하고 싶습니다 . 우리는 변환 할 수 있습니다 IEnumerable<Tiger>IEnumerable<Animal>안전하게? 에 기린 을 입력 할 방법이 없기 때문입니다 IEnumerable<Animal>. 왜를로 변환 할 IComparable<Animal>IComparable<Tiger>있습니까? 에서 기린을 꺼내는 방법이 없기 때문입니다 IComparable<Animal>. 말이 되나요?
Eric Lippert

111

아마도 예제를 제공하는 것이 가장 쉬운 방법 일 것입니다.

공분산

정식 예 : IEnumerable<out T>,Func<out T>

당신은에서 변환 할 수 있습니다 IEnumerable<string>IEnumerable<object>, 또는 Func<string>Func<object>. 값 이러한 객체 에서만 나옵니다 .

API에서 값을 가져오고 특정 (예 string:)을 반환하면 반환 된 값을보다 일반적인 유형 (예 :)으로 처리 할 수 있기 때문에 작동합니다 object.

공분산

정식 예 : IComparer<in T>,Action<in T>

당신은에서 변환 할 수 있습니다 IComparer<object>IComparer<string>, 또는 Action<object>Action<string>; 값 이러한 객체 에만 적용 됩니다.

이번에는 API가 일반적인 (예 :)을 기대 object하면 더 구체적인 (예 :)을 줄 수 있기 때문에 작동합니다 string.

더 일반적으로

당신이 인터페이스가있는 경우 IFoo<T>가에서 공변 될 수 T로 (즉, 그것을 선언 할 IFoo<out T>경우 T에만 인터페이스 내에서 (예 복귀 형) 출력 위치에 사용된다. 그것은에 contravariant 수 있습니다 T(예 : IFoo<in T>경우) T만 입력 위치에 사용됩니다 ( 예를 들어 매개 변수 유형).

"출력 위치"가 들리는 것처럼 간단하지 않기 때문에 혼동 될 수 있습니다. 유형의 매개 변수 Action<T>는 여전히 T출력 위치 에서만 사용 Action<T>합니다. 의미가 무엇인지 알면 회전 의 모순이 변합니다. 이 값은 본 방법의 구현에서 전달할 수있는 "출력"의 방향으로 그냥 반환 값 수있는 것처럼, 발신자 번호. 일반적으로 이런 종류의 일은 안타깝게도 :)


1
나와 같은 사람에게는 공변량이 아닌 것과 반 변하지 않는 것과 둘 다가 아닌 것을 보여주는 예제를 추가하는 것이 좋을 것입니다.
bjan

1
@ Jon Skeet 좋은 예, "유형의 매개 변수 Action<T>는 여전히 T출력 위치 에서만 사용 하고 있습니다 " 만 이해하지 못합니다 . Action<T>반환 유형이 무효입니다. 어떻게 T출력으로 사용할 수 있습니까? 아니면 그것이 규칙을 위반할 수 없다는 것을 볼 수있는 것을 반환하지 않기 때문에 그것이 의미하는 것입니까?
Alexander Derck

2
차이를 다시 배우기 위해이 훌륭한 답변으로 다시 돌아 오는 내 미래의 자아에게 , 이것은 당신이 원하는 라인 입니다. 특정 문자열 (예 : 문자열)의 경우 반환 된 값을보다 일반적인 유형 (객체와 같은)으로 처리 할 수 ​​있습니다. "
매트 클라인

이 모든 것의 가장 혼란스러운 부분은 공분산 또는 반공산의 경우 방향을 무시하거나 (in 또는 out) 더 일반적인 변환에 더 구체적으로 나타납니다! 내 말은 :은 "당신은 (오브젝트 같은) 더 일반적인 유형으로 그 반환 값을 처리 할 수있다" 공분산 및 "API는 (객체와 같은) 뭔가 장군을 기대하고 당신이 뭔가에게 그것을 (문자열 등)보다 구체적인를 제공 할 수 있습니다"에 대한 contravariance . 나에게 이런 소리 종류는 동일합니다!
XMight

@AlexanderDerck : 왜 내가 전에 답장을하지 않았는지 잘 모르겠습니다. 나는 그것이 명확하지 않다는 데 동의하고 그것을 명확히하려고 노력할 것입니다.
Jon Skeet

16

내 게시물이 언어에 구애받지 않고 주제를 보는 데 도움이되기를 바랍니다.

우리의 내부 교육을 위해 나는 "Smalltalk, Objects and Design (Chamond Liu)"이라는 훌륭한 책을 다루었 고 다음과 같은 사례를 다시 설명했습니다.

"일관성"이란 무엇입니까? 아이디어는 대체 가능한 유형으로 유형 안전 유형 계층을 설계하는 것입니다. 정적으로 유형이 지정된 언어로 작업하는 경우 이러한 일관성을 유지하는 핵심은 하위 유형 기반 적합성입니다. (여기서는 Liskov 대체 원칙 (LSP)에 대해 자세히 설명합니다.)

실제 예 (의사 코드 / C #에서 유효하지 않음) :

  • 공분산 : 정적 타이핑을 사용하여 계란을“일관되게”배치하는 조류를 가정 해 봅시다. 조류 유형이 계란을 배치하는 경우 조류의 하위 유형이 계란의 하위 유형을 배치하지 않습니까? 예를 들어 Duck 유형은 DuckEgg를 배치하고 일관성이 부여됩니다. 이것이 왜 일관성이 있습니까? 그러한 표현에서 : Egg anEgg = aBird.Lay();참조 aBird는 Bird 또는 Duck 인스턴스에 의해 법적으로 대체 될 수 있습니다. 리턴 타입은 Lay ()가 정의 된 타입과 공변 적이라고합니다. 하위 유형의 재정의는보다 특수한 유형을 반환 할 수 있습니다. =>“더 많은 것을 제공합니다.”

  • Contravariance : 피아노가 정적 타이핑으로 "일관되게"연주 할 수 있다고 가정 해 봅시다. Pianist가 피아노를 연주하면 GrandPiano를 연주 할 수 있습니까? 오히려 Virtuoso가 GrandPiano를 연주하지 않습니까? (경고하십시오; 비틀림이 있습니다!) 이것은 일치하지 않습니다! 그러한 표현에서 : aPiano.Play(aPianist);aPiano는 피아노 나 GrandPiano 인스턴스로 합법적으로 대체 될 수 없었습니다! GrandPiano는 Virtuoso 만 연주 할 수 있으며 피아니스트는 너무 일반적입니다! GrandPiano는보다 일반적인 형식으로 연주 할 수 있어야하며, 그 결과는 일관됩니다. 우리는 매개 변수 유형이 Play ()가 정의 된 유형과 모순된다고 말합니다. 하위 유형의 재정의는보다 일반적인 유형을 허용 할 수 있습니다. =>“그들은 덜 필요합니다.”

C #으로 돌아 가기 :
C #은 기본적으로 정적으로 유형이 지정된 언어이므로, 해당 유형의 일관된 사용 / 개발을 보장하기 위해 공존 또는 반 변형이어야하는 유형 인터페이스의 "위치"(예 : 매개 변수 및 반환 유형)를 명시 적으로 표시해야합니다. LSP가 제대로 작동하도록합니다. 동적으로 유형이 지정된 언어에서 LSP 일관성은 일반적으로 문제가되지 않습니다. 즉, 유형에 동적 유형 만 사용하는 경우 .Net 인터페이스 및 델리게이트에서 공변량 및 반 변형 "마크 업"을 완전히 제거 할 수 있습니다. 그러나 이것은 C #에서 가장 좋은 솔루션은 아닙니다 (공용 인터페이스에서는 동적을 사용해서는 안 됨).

이론으로 돌아 가기 :
설명 된 적합성 (공변량 리턴 유형 / 반 변량 매개 변수 유형)은 이론적 이상 (언어 Emerald 및 POOL-1에서 지원)입니다. 일부 oop 언어 (예 : Eiffel)는 다른 유형의 일관성 (예 : esp)을 적용하기로 결정했습니다. 공변량 모수 유형도 이론적 이상보다 현실을 더 잘 설명하기 때문입니다. 정적으로 유형이 지정된 언어에서는 "이중 디스패치"및 "방문자"와 같은 디자인 패턴을 적용하여 원하는 일관성을 유지해야합니다. 다른 언어는 소위 "다중 디스패치"또는 다중 메소드 (기본적으로 런타임에 함수 과부하를 선택 하는 것 (예 : CLOS))를 제공하거나 동적 타이핑을 사용하여 원하는 효과를 얻습니다.


하위 유형의 재정의가 더 특수한 유형을 반환 할 수 있다고 합니다 . 그러나 그것은 완전히 사실이 아닙니다. 을 Bird정의 public abstract BirdEgg Lay();하면 Duck : Bird 반드시 구현 해야public override BirdEgg Lay(){} 하므로 BirdEgg anEgg = aBird.Lay();모든 종류의 분산이있는 어설 션 은 사실이 아닙니다. 설명의 요점을 전제로하여 요점 전체가 사라졌습니다. 대신 DuckEgg가 내재적으로 BirdEgg 출력 / 반환 유형으로 캐스트되는 구현 내에 공분산이 존재한다고 말 하시겠습니까 ? 어느 쪽이든, 혼란을 제거하십시오.
Suamere

1
짧게 자르려면 : 당신 말이 맞아요! 혼란을 드려 죄송합니다. C #에서DuckEgg Lay() 유효한 재정의가 아니며 이것이 핵심입니다. C #은 공변량 리턴 유형을 지원하지 않지만 C ++뿐만 아니라 Java도 지원합니다. 오히려 C #과 같은 구문을 사용하여 이론적 이상을 설명했습니다. C #에서는 Bird와 Duck이 공통 인터페이스를 구현할 수 있도록해야합니다. Lay는 공변량 리턴 (즉, 사양 외) 유형을 갖도록 정의 된 다음 문제가 함께 맞습니다! Egg Lay()
니코

1
@ Jon-Skeet의 답변에 대한 Matt-Klein의 의견과 유사하게, "나의 미래에 대한 자기 자신에게": "더 많은 것을 제공합니다"(구체적으로) 및 "그들은 더 적게 요구합니다"(구체적으로)입니다. "더 적게 요구하고 더 많은 것을 배달하십시오"는 훌륭한 니모닉입니다! 덜 구체적인 지침 (일반 요청)이 필요하지만 더 구체적인 내용 (실제 작업 제품)을 제공하고자하는 직업과 유사합니다. 어느 쪽이든 하위 유형의 순서 (LSP)는 손상되지 않습니다.
karfus

@karfus : 감사합니다.하지만 다른 소스에서 "더 적게 요구하고 더 많은 것을 제공"한다는 생각을 들었습니다. 위에서 언급 한 Liu의 책일 수도 있고 .NET Rock 토크 일 수도 있습니다. Btw. 자바에서 사람들은 니모닉을 "PECS"로 줄 였는데, 이는 차이를 선언하는 구문 방식과 직접 관련이 있으며 PECS는 "프로듀서 extends, 소비자 super"를위한 것입니다.
니코

5

변환기 대리자는 차이점을 이해하는 데 도움이됩니다.

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutput메소드가 보다 구체적인 유형을 리턴하는 공분산을 나타냅니다 .

TInput메소드가 덜 구체적인 유형으로 전달되는 반공 분산을 나타냅니다 .

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();

0

Co와 Contra 분산은 매우 논리적입니다. 언어 유형 시스템은 실제 논리를 지원하도록합니다. 예를 들어 이해하기 쉽습니다.

공분산

예를 들어, 당신은 꽃을 사고 싶어하고 당신은 당신의 도시에 두 개의 꽃 가게가 있습니다 : 장미 가게와 데이지 가게.

누군가에게 "꽃집은 어디에 있습니까?"라고 물으면 누군가 장미 가게가 어디 있는지 말해 주 겠어요? 예, 장미는 꽃이기 때문에 꽃을 사고 싶다면 장미를 구입할 수 있습니다. 누군가가 데이지 가게의 주소로 답장을 한 경우에도 마찬가지입니다.

이것은의 예입니다 공분산 : 당신은 캐스트에 허용 A<C>A<B>여기서 C의 서브 클래스 인 B경우, A(함수에서 결과로 반환) 일반적인 값을 생성합니다. 공분산은 생산자에 관한 것이므로 C # out은 공분산에 키워드 를 사용합니다 .

유형 :

class Flower {  }
class Rose: Flower { }
class Daisy: Flower { }

interface FlowerShop<out T> where T: Flower {
    T getFlower();
}

class RoseShop: FlowerShop<Rose> {
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop: FlowerShop<Daisy> {
    public Daisy getFlower() {
        return new Daisy();
    }
}

질문은 "꽃집은 어디입니까?", 대답은 "장미 가게는"입니다.

static FlowerShop<Flower> tellMeShopAddress() {
    return new RoseShop();
}

공분산

예를 들어 여자 친구에게 꽃을 선물하고 여자 친구는 꽃을 좋아합니다. 당신은 그녀를 장미를 사랑하는 사람 또는 데이지를 사랑하는 사람으로 생각할 수 있습니까? 그녀가 꽃을 좋아한다면 장미와 데이지를 좋아하기 때문입니다.

이것은의 예입니다 contravariance : 당신이 캐스트 허용하고 A<B>A<C>, C의 서브 클래스 인 B경우, A소비하는 일반적인 값. in반공 분산은 소비자에 관한 것이므로 C # 에서 반공 분산에 키워드 를 사용합니다 .

유형 :

interface PrettyGirl<in TFavoriteFlower> where TFavoriteFlower: Flower {
    void takeGift(TFavoriteFlower flower);
}

class AnyFlowerLover: PrettyGirl<Flower> {
    public void takeGift(Flower flower) {
        Console.WriteLine("I like all flowers!");
    }
}

당신은 장미를 사랑하는 사람으로 꽃을 사랑하는 여자 친구를 생각하고 장미를주는 것입니다.

PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

연결

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.