协变和逆变现实世界的例子

我在理解我将如何在现实世界中使用协变和反变化有点麻烦。

到目前为止,我见过的唯一例子是旧的数组例子。

object[] objectArray = new string[] { "string 1", "string 2" }; 

如果我能看到它在其他地方被使用,那么看到一个例子可以让我在开发过程中使用它。

比方说,你有一个class级人员和一个class级,老师。 你有一些以IEnumerable<Person>作为参数的操作。 在你的School类中你有一个方法返回一个IEnumerable<Teacher> 。 协方差允许您直接使用该结果作为采用IEnumerable<Person>的方法,将派生types更多的派生types(更通用)。 直观的逆变换允许您使用更通用的types,其中指定了更多的派生types。 另见https://msdn.microsoft.com/en-us/library/dd799517.aspx

 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 

input和输出关键字用于控制编译器的接口和委托的转换规则:

 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 
 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>' } 

基本上,每当你有一个函数使用一个Enumerabletypes的时候,你就不能传入一个派生types的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中的变化行为可能会引入微妙和难以find的错误,如果你使用这样的构造。

来自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; } 

这是一个使用inheritance层次结构的简单示例。

协方差

协方差广泛用于不可变集合(即,不能添加或从集合中删除新元素)

给定简单的类层次结构:

 public abstract class LifeForm { } public abstract class Animal : LifeForm { } public class Giraffe : Animal { } public class Zebra : Animal { } 

看似这样的方法

 public static void DoSomethingWithLifeForms(IList<LifeForm> lifeForms) { foreach (var lifeForm in lifeForms) { Console.WriteLine(lifeForm.GetType().ToString()); } } 

…应该接受一个异构的收集(它确实)

 var myAnimals = new List<LifeForm> { new Giraffe(), new Zebra() }; DoSomethingWithLifeForms(myAnimals); // Giraffe, Zebra 

但是,传递更多派生types的集合失败!

 var myGiraffes = new List<Giraffe> { new Giraffe(), // "Jerry" new Giraffe() // "Melman" }; DoSomethingWithLifeForms(myGiraffes); // Compile Error! 

cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'

为什么? 因为generics参数IList<LifeForm>不是协变的,所以IList<LifeForm>是不变的,只接受集合(实现IList),参数化typesT必须是LifeForm

如果我恶意更改方法实现(但保留相同的方法签名),编译器阻止传递List<Giraffe>的原因将变得明显:

  public static void DoSomethingWithLifeForms(IList<LifeForm> lifeForms) { lifeForms.Add(new Zebra()); } 

由于IList允许添加或删除元素,所以LifeForm任何子类LifeForm可以被添加到参数lifeForms ,并且会违反传递给该方法的派生types集合的types。 (在这里,恶意方法会尝试添加Zebravar myGiraffes )。 幸运的是,编译器保护我们免受这种危险。

解决scheme是确保使用协变genericstypes,例如IEnumerable (定义为IEnumerable<out T> )。 这可以防止对集合的更改,因此,任何具有LifeForm子types的LifeForm现在都可以传递给该方法:

 public static void DoSomethingWithLifeForms(IEnumerable<LifeForm> lifeForms) { foreach (var lifeForm in lifeForms) { Console.WriteLine(lifeForm.GetType().ToString()); } } 

DoSomethingWithLifeForms()现在可以用ZebrasGiraffesLifeForm任何子类来LifeForm

逆变

函数作为parameter passing时,经常使用逆变函数。

下面是一个函数的例子,它将一个Action<Zebra>作为参数,并在Zebra的已知实例上调用它:

 public void PerformZebraAction(Action<Zebra> zebraAction) { var zebraToAction = new Zebra(); zebraAction(zebraToAction); } 

正如所料,这工作得很好:

 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<object>作为需要Action<Zebra>的parameter passing?),如果解压缩这些步骤,则会注意到被调用的函数( PerformZebraAction )本身是负责的用于将Zebra实例传递给函数,而不是调用代码。

由于以这种方式使用高阶函数的反向方法,在调用Action ,它是对zebraAction函数(作为parameter passing)调用的派生得更多的对象实例,它本身使用较less的派生types。

转换器委托帮助我可视化两个概念一起工作:

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

TOutput表示方法返回更具体types的 协方差

TInput表示一个方法传递一个较不具体的types的 逆变

 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();