공분산 및 공분산 실제 사례
현실 세계에서 공분산과 공분산을 사용하는 방법을 이해하는 데 약간의 어려움이 있습니다.
지금까지 내가 본 유일한 예는 동일한 이전 배열 예입니다.
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<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
.
If the method implementation of PrintLifeForms
was malicious (but has same method signature), the reason why the compiler prevents passing List<Giraffe>
becomes obvious:
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
lifeForms.Add(new Zebra());
}
Since IList
permits adding or removal of elements, any subclass of LifeForm
could thus be added to the parameter lifeForms
, and would violate the type of any collection of derived types passed to the method. (Here, the malicious method would attempt to add a Zebra
to var myGiraffes
). Fortunately, the compiler protects us from this danger.
Covariance (Generic with parameterized type decorated with out
)
Covariance is widely used with immutable collections (i.e. where new elements cannot be added or removed from a collection)
The solution to the example above is to ensure that a covariant generic collection type is used, e.g. IEnumerable
(defined as IEnumerable<out T>
). IEnumerable
has no methods to change to the collection, and as a result of the out
covariance, any collection with subtype of LifeForm
may now be passed to the method:
public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
PrintLifeForms
can now be called with Zebras
, Giraffes
and any IEnumerable<>
of any subclass of LifeForm
Contravariance (Generic with parameterized type decorated with in
)
Contravariance is frequently used when functions are passed as parameters.
Here's an example of a function, which takes an Action<Zebra>
as a parameter, and invokes it on a known instance of a Zebra:
public void PerformZebraAction(Action<Zebra> zebraAction)
{
var zebra = new Zebra();
zebraAction(zebra);
}
As expected, this works just fine:
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra
Intuitively, this will fail:
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction);
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
However, this succeeds
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal
and even this also succeeds:
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba
Why? Because Action
is defined as Action<in T>
, i.e. it is contravariant
, meaning that for Action<Zebra> myAction
, that myAction
can be at "most" a Action<Zebra>
, but less derived superclasses of Zebra
are also acceptable.
Although this may be non-intuitive at first (e.g. how can an Action<object>
be passed as a parameter requiring Action<Zebra>
?), if you unpack the steps, you will note that the called function (PerformZebraAction
) itself is responsible for passing data (in this case a Zebra
instance) to the function - the data doesn't come from the calling code.
Because of the inverted approach of using higher order functions in this manner, by the time the Action
is invoked, it is the more derived Zebra
instance which is invoked against the zebraAction
function (passed as a parameter), although the function itself uses a less derived type.
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>'
}
Basically whenever you had a function that takes an Enumerable of one type, you couldn't pass in an Enumerable of a derived type without explicitly casting it.
Just to warn you about a trap though:
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");
}
That is horrible code anyway, but it does exist and the changing behavior in C# 4 might introduce subtle and hard to find bugs if you use a construct like this.
From MSDN
The following code example shows covariance and contravariance support for method groups
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;
}
The converter delegate helps me to visualise both concepts working together:
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();
참고 URL : https://stackoverflow.com/questions/2662369/covariance-and-contravariance-real-world-example
'Programing' 카테고리의 다른 글
Base64 Java 문자열 인코딩 및 디코딩 (0) | 2020.06.14 |
---|---|
일반적인 CSS 미디어 쿼리 중단 점 (0) | 2020.06.14 |
파이썬은 텍스트 파일을 연결 (0) | 2020.06.14 |
OSX에서 zsh에서 bash로 전환했다가 다시? (0) | 2020.06.14 |
Linux-redis-cli 만 설치 (0) | 2020.06.14 |