답변:
문제는 "공분산과 공분산의 차이점은 무엇입니까?"입니다.
공분산 및 반공 분산은 집합의 한 멤버를 다른 멤버와 연결하는 매핑 함수의 속성입니다 . 보다 구체적으로, 맵핑은 그 세트에 대한 관계 와 관련하여 공변량 또는 반 변형 일 수있다 .
모든 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의 공분산과 공분산의 차이입니다. 공분산 은 할당의 방향을 유지 합니다. 불균형은 반대로 됩니다.
IEnumerable<Tiger>
에 IEnumerable<Animal>
안전하게? 에 기린 을 입력 할 방법이 없기 때문입니다 IEnumerable<Animal>
. 왜를로 변환 할 IComparable<Animal>
수 IComparable<Tiger>
있습니까? 에서 기린을 꺼내는 방법이 없기 때문입니다 IComparable<Animal>
. 말이 되나요?
아마도 예제를 제공하는 것이 가장 쉬운 방법 일 것입니다.
공분산
정식 예 : 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>
합니다. 의미가 무엇인지 알면 회전 의 모순이 변합니다. 이 값은 본 방법의 구현에서 전달할 수있는 "출력"의 방향으로 그냥 반환 값 수있는 것처럼, 발신자 번호. 일반적으로 이런 종류의 일은 안타깝게도 :)
Action<T>
는 여전히 T
출력 위치 에서만 사용 하고 있습니다 " 만 이해하지 못합니다 . Action<T>
반환 유형이 무효입니다. 어떻게 T
출력으로 사용할 수 있습니까? 아니면 그것이 규칙을 위반할 수 없다는 것을 볼 수있는 것을 반환하지 않기 때문에 그것이 의미하는 것입니까?
내 게시물이 언어에 구애받지 않고 주제를 보는 데 도움이되기를 바랍니다.
우리의 내부 교육을 위해 나는 "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 출력 / 반환 유형으로 캐스트되는 구현 내에 공분산이 존재한다고 말 하시겠습니까 ? 어느 쪽이든, 혼란을 제거하십시오.
DuckEgg Lay()
유효한 재정의가 아니며 이것이 핵심입니다. C #은 공변량 리턴 유형을 지원하지 않지만 C ++뿐만 아니라 Java도 지원합니다. 오히려 C #과 같은 구문을 사용하여 이론적 이상을 설명했습니다. C #에서는 Bird와 Duck이 공통 인터페이스를 구현할 수 있도록해야합니다. Lay는 공변량 리턴 (즉, 사양 외) 유형을 갖도록 정의 된 다음 문제가 함께 맞습니다! Egg Lay()
extends
, 소비자 super
"를위한 것입니다.
변환기 대리자는 차이점을 이해하는 데 도움이됩니다.
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();
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());