[C#] 공분산 및 반 공예 실제 사례
현실 세계에서 공분산과 공분산을 사용하는 방법을 이해하는 데 약간의 어려움이 있습니다.
지금까지 내가 본 유일한 예는 동일한 이전 배열 예입니다.
object[] objectArray = new string[] { "string 1", "string 2" };
다른 곳에서 사용되는 것을 볼 수 있다면 개발 중에 사용할 수있는 예제를 보는 것이 좋을 것입니다.
답변
Person 클래스와 그 클래스에서 파생 된 클래스가 있다고 가정 해 봅시다. IEnumerable<Person>
인수로 사용되는 몇 가지 작업이 있습니다 . School 클래스에는을 반환하는 메서드가 있습니다 IEnumerable<Teacher>
. 공분산을 사용하면을 취하는 메소드에 대해 그 결과를 직접 사용할 수 있으며 IEnumerable<Person>
, 파생이 덜 된 (보다 일반적인) 유형으로 더 파생 된 유형을 대체 할 수 있습니다. 반 직관적으로 반대 분산을 사용하면보다 파생 된 유형이 지정된 더 일반적인 유형을 사용할 수 있습니다.
MSDN에서 Generics의 공분산 및 공분산을 참조하십시오 .
수업 :
public class Person
{
public string Name { get; set; }
}
public class Teacher : Person { }
public class MailingList
{
public void Add(IEnumerable<out Person> people) { ... }
}
public class School
{
public IEnumerable<Teacher> GetTeachers() { ... }
}
public class PersonNameComparer : IComparer<Person>
{
public int Compare(Person a, Person b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : Compare(a,b);
}
private int Compare(string a, string b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.CompareTo(b);
}
}
사용법 :
var teachers = school.GetTeachers();
var mailingList = new MailingList();
// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);
// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());
답변
// Contravariance
interface IGobbler<in T> {
void gobble(T t);
}
// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());
// Covariance
interface ISpewer<out T> {
T spew();
}
// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();
완전성을 위해…
// Invariance
interface IHat<T> {
void hide(T t);
T pull();
}
// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();
// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat; // Compiler error
// …because…
mHat.hide(new Dolphin()); // Hide a dolphin in a rabbit hat??
// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat; // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull(); // Pull a marsh rabbit out of a cottontail hat??
답변
차이점을 이해하도록 돕기 위해 함께 정리 한 내용은 다음과 같습니다.
public interface ICovariant<out T> { }
public interface IContravariant<in T> { }
public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }
public class Fruit { }
public class Apple : Fruit { }
public class TheInsAndOuts
{
public void Covariance()
{
ICovariant<Fruit> fruit = new Covariant<Fruit>();
ICovariant<Apple> apple = new Covariant<Apple>();
Covariant(fruit);
Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
}
public void Contravariance()
{
IContravariant<Fruit> fruit = new Contravariant<Fruit>();
IContravariant<Apple> apple = new Contravariant<Apple>();
Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
Contravariant(apple);
}
public void Covariant(ICovariant<Fruit> fruit) { }
public void Contravariant(IContravariant<Apple> apple) { }
}
tldr
ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant
답변
in 및 out 키워드는 일반 매개 변수를 사용하여 인터페이스 및 델리게이트에 대한 컴파일러의 캐스팅 규칙을 제어합니다.
interface IInvariant<T> {
// This interface can not be implicitly cast AT ALL
// Used for non-readonly collections
IList<T> GetList { get; }
// Used when T is used as both argument *and* return type
T Method(T argument);
}//interface
interface ICovariant<out T> {
// This interface can be implicitly cast to LESS DERIVED (upcasting)
// Used for readonly collections
IEnumerable<T> GetList { get; }
// Used when T is used as return type
T Method();
}//interface
interface IContravariant<in T> {
// This interface can be implicitly cast to MORE DERIVED (downcasting)
// Usually means T is used as argument
void Method(T argument);
}//interface
class Casting {
IInvariant<Animal> invariantAnimal;
ICovariant<Animal> covariantAnimal;
IContravariant<Animal> contravariantAnimal;
IInvariant<Fish> invariantFish;
ICovariant<Fish> covariantFish;
IContravariant<Fish> contravariantFish;
public void Go() {
// NOT ALLOWED invariants do *not* allow implicit casting:
invariantAnimal = invariantFish;
invariantFish = invariantAnimal; // NOT ALLOWED
// ALLOWED covariants *allow* implicit upcasting:
covariantAnimal = covariantFish;
// NOT ALLOWED covariants do *not* allow implicit downcasting:
covariantFish = covariantAnimal;
// NOT ALLOWED contravariants do *not* allow implicit upcasting:
contravariantAnimal = contravariantFish;
// ALLOWED contravariants *allow* implicit downcasting
contravariantFish = contravariantAnimal;
}//method
}//class
// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }
class Delegates {
// When T is used as both "in" (argument) and "out" (return value)
delegate T Invariant<T>(T argument);
// When T is used as "out" (return value) only
delegate T Covariant<out T>();
// When T is used as "in" (argument) only
delegate void Contravariant<in T>(T argument);
// Confusing
delegate T CovariantBoth<out T>(T argument);
// Confusing
delegate T ContravariantBoth<in T>(T argument);
// From .NET Framework:
public delegate void Action<in T>(T obj);
public delegate TResult Func<in T, out TResult>(T arg);
}//class
답변
상속 계층 구조를 사용하는 간단한 예는 다음과 같습니다.
간단한 클래스 계층이 주어지면 :
그리고 코드에서 :
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
불일치 (예 : 또는 키워드로 장식 되지 않은 일반 유형 매개 변수 )in
out
겉보기에는 이와 같은 방법
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
… 이기종 콜렉션을 받아 들여야합니다.
var myAnimals = new List<LifeForm>
{
new Giraffe(),
new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra
그러나 더 파생 된 형식 의 컬렉션을 전달 하면 실패합니다!
var myGiraffes = new List<Giraffe>
{
new Giraffe(), // "Jerry"
new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!
cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'
왜? 일반 매개 변수 IList<LifeForm>
는 공변량이 아니므
IList<T>
로 변하지 않으므로 IList<LifeForm>
매개 변수화 된 유형이 T
있어야 하는 컬렉션 (IList를 구현하는) 만 허용 합니다 LifeForm
.
의 메소드 구현 PrintLifeForms
이 악의적이지만 (동일한 메소드 서명이있는 경우) 컴파일러가 통과를 막는 이유 List<Giraffe>
는 명백합니다.
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
lifeForms.Add(new Zebra());
}
이후 IList
허가 추가 또는 요소의 제거 중 어느 서브 클래스 LifeForm
따라서 파라미터에 추가 될 수 lifeForms
및 유도 된 유형의 수집의 종류에 위반이 메소드로 전달. (여기서 악의적 인 방법은에를 추가하려고 시도 Zebra
합니다 var myGiraffes
). 다행히 컴파일러는 이러한 위험으로부터 우리를 보호합니다.
공분산 (로 장식 된 매개 변수화 된 유형의 일반 out
)
공분산은 불변 컬렉션에 널리 사용됩니다 (즉, 컬렉션에서 새 요소를 추가하거나 제거 할 수없는 경우)
위의 예에 대한 해결책은 공변량 제네릭 수집 유형이 사용되도록하는 것입니다 IEnumerable
( 예 :로 정의 됨 IEnumerable<out T>
). IEnumerable
컬렉션으로 변경할 방법이 없으며 out
공분산 의 결과 로 하위 유형이있는 컬렉션 LifeForm
이 이제 메서드에 전달 될 수 있습니다.
public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
PrintLifeForms
지금 호출 할 수 있습니다 Zebras
, Giraffes
그리고 어떤 IEnumerable<>
의 서브 클래스의LifeForm
불균형 (로 장식 된 매개 변수화 된 유형의 일반 in
)
공분산은 함수가 매개 변수로 전달 될 때 자주 사용됩니다.
다음 Action<Zebra>
은 매개 변수로 매개 변수를 사용하여 알려진 Zebra 인스턴스에서 호출 하는 함수의 예입니다 .
public void PerformZebraAction(Action<Zebra> zebraAction)
{
var zebra = new Zebra();
zebraAction(zebra);
}
예상대로 이것은 잘 작동합니다.
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra
직관적으로, 이것은 실패합니다 :
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction);
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
그러나 이것은 성공합니다
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal
심지어 이것도 성공합니다.
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba
왜? 때문에 Action
같이 정의된다 Action<in T>
, 즉, 그것이 contravariant
대한 것을 의미 Action<Zebra> myAction
하는, myAction
“대부분의”에있을 수 Action<Zebra>
의 수퍼 미만 유도는 Zebra
도 가능하다.
처음에는 직관적이지 않을 수도 있지만 (예 : ?를 Action<object>
요구하는 매개 변수로 전달 하는 방법 Action<Zebra>
) 단계를 풀면 호출 된 함수 ( PerformZebraAction
) 자체가 데이터 전달 (이 경우 Zebra
인스턴스)을 담당합니다. )를 함수에-데이터가 호출 코드에서 나오지 않습니다.
이러한 방식으로 고차 함수를 사용하는 역 접근 방식 때문에 함수 Action
가 호출 될 때 함수 자체가 덜 파생 된 유형을 사용하더라도 함수 Zebra
에 대해 호출되는 것이 더 파생 된 인스턴스입니다 zebraAction
(파라미터로 전달됨).
답변
class A {}
class B : A {}
public void SomeFunction()
{
var someListOfB = new List<B>();
someListOfB.Add(new B());
someListOfB.Add(new B());
someListOfB.Add(new B());
SomeFunctionThatTakesA(someListOfB);
}
public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
// Before C# 4, you couldn't pass in List<B>:
// cannot convert from
// 'System.Collections.Generic.List<ConsoleApplication1.B>' to
// 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}
기본적으로 한 유형의 Enumerable을 사용하는 함수가있을 때마다 명시 적으로 캐스팅하지 않고 파생 유형의 Enumerable을 전달할 수 없습니다.
그래도 함정에 대해 경고하기 위해 :
var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
// In C# 4, this branch will
// execute...
Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
// ...but in C# 3 and earlier,
// this one will execute instead.
Console.Write("It is B");
}
어쨌든 그것은 끔찍한 코드이지만 존재하지만 C # 4의 변화하는 동작은 이와 같은 구문을 사용하면 미묘하고 버그를 찾기가 어려울 수 있습니다.
답변
에서 MSDN
다음 코드 예제는 메소드 그룹에 대한 공분산 및 공분산 지원을 보여줍니다.
static object GetObject() { return null; }
static void SetObject(object obj) { }
static string GetString() { return ""; }
static void SetString(string str) { }
static void Test()
{
// Covariance. A delegate specifies a return type as object,
// but you can assign a method that returns a string.
Func<object> del = GetString;
// Contravariance. A delegate specifies a parameter type as string,
// but you can assign a method that takes an object.
Action<string> del2 = SetObject;
}