仍然困惑协变和逆变和进/出

好的,我读了一下关于这个话题在stackoverflow,看这个 & 这个 ,但仍然有点混淆共同/反方差。

从这里

协方差允许在原始types仅用于“输出”位置(例如作为返回值)的API中用“较大”(较不具体的)types替代。 反变换允许在原始types仅用于“input”位置的API中replace“更小”(更具体)的types。

我知道它与types安全有关。

关于in/out事情。 我可以说我用什么时候需要写信给它,什么时候只读它。 并in相反方差的方式out 。 但从上面的解释…

在这里

例如, List<Banana>不能被当作List<Fruit>处理,因为list.Add(new Apple())对List是有效的,对于List<Banana>不是。

所以不应该这样,如果我要用来写信给对象,它必须更大一些。

我知道这个问题已经被问到,但仍然非常困惑。

C#4.0中的协变和逆变都指的是使用派生类而不是基类的能力。 input/输出关键字是编译器提示,用于指示types参数是否将用于input和输出。

协方差

C#4.0中的协变性由out关键字提供,这意味着使用outtypes参数的派生类的genericstypes是OK的。 于是

 IEnumerable<Fruit> fruit = new List<Apple>(); 

由于Apple是一种FruitList<Apple>可以安全地用作IEnumerable<Fruit>

逆变

反向变化是in关键字,它表示inputtypes,通常在代表中。 原理是一样的,这意味着委托可以接受更多的派生类。

 public delegate void Func<in T>(T param); 

这意味着如果我们有一个Func<Fruit> ,它可以被转换成Func<Apple>

 Func<Fruit> fruitFunc = (fruit)=>{}; Func<Apple> appleFunc = fruitFunc; 

如果他们基本上是一样的话,为什么他们被称为co / contravariance?

因为即使原理是相同的,从派生到基础的安全转换,当用于inputtypes时,我们可以安全地将更less派生types( Func<Fruit> )转换为更多派生types( Func<Apple> ),是有道理的,因为任何带Fruitfunction,也可以拿Apple

我不得不长时间思考如何解释这一点。 解释似乎和理解一样困难。

想象一下,你有一个基类水果。 你有两个苹果和香蕉的小类。

  Fruit / \ Banana Apple 

你创build两个对象:

 Apple a = new Apple(); Banana b = new Banana(); 

对于这两个对象,你可以将它们转换成水果对象。

 Fruit f = (Fruit)a; Fruit g = (Fruit)b; 

你可以把派生类视为它们的基类。

但是,你不能像一个派生类那样对待基类

 a = (Apple)f; //This is incorrect 

让我们把这个应用到List例子中。

假设你创build了两个列表:

 List<Fruit> fruitList = new List<Fruit>(); List<Banana> bananaList = new List<Banana>(); 

你可以做这样的事情…

 fruitList.Add(new Apple()); 

 fruitList.Add(new Banana()); 

因为它基本上是在将它们添加到列表中时对它们进行types化。 你可以这样想…

 fruitList.Add((Fruit)new Apple()); fruitList.Add((Fruit)new Banana()); 

然而,将相同的逻辑应用于相反的情况引发了一些红旗。

 bananaList.Add(new Fruit()); 

是相同的

 bannanaList.Add((Banana)new Fruit()); 

因为不能像派生类那样处理基类,所以会产生错误。

以防万一你的问题是为什么这会导致错误我也会解释。

这是Fruit类

 public class Fruit { public Fruit() { a = 0; } public int A { get { return a; } set { a = value } } private int a; } 

这里是香蕉class

 public class Banana: Fruit { public Banana(): Fruit() // This calls the Fruit constructor { // By calling ^^^ Fruit() the inherited variable a is also = 0; b = 0; } public int B { get { return b; } set { b = value; } } private int b; } 

所以想象你再次创造了两个对象

 Fruit f = new Fruit(); Banana ba = new Banana(); 

请记住,香蕉有两个variables“a”和“b”,而水果只有一个“a”。 所以当你这样做…

 f = (Fruit)b; fA = 5; 

您创build一个完整的水果对象。 但如果你这样做…

 ba = (Banana)f; ba.A = 5; ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist? 

问题是你不会创build一个完整的Banana类。不是所有的数据成员都被声明/初始化。

现在,我从淋浴回来,并得到了我的自己一个小吃inheritance人,有点复杂。

事后看来,在进入复杂的东西时,我应该放弃这个比喻

让我们做两个新的类:

 public class Base public class Derived : Base 

他们可以做任何你喜欢的事情

现在让我们定义两个函数

 public Base DoSomething(int variable) { return (Base)DoSomethingElse(variable); } public Derived DoSomethingElse(int variable) { // Do stuff } 

这就好像“out”是如何工作的,你应该总是能够像派生类一样使用派生类,让它应用到一个接口

 interface MyInterface<T> { T MyFunction(int variable); } 

out / in之间的主要区别是当Generic被用作返回types或方法参数时,这是前一种情况。

让我们定义一个实现这个接口的类:

 public class Thing<T>: MyInterface<T> { } 

那么我们创build两个对象:

 MyInterface<Base> base = new Thing<Base>; MyInterface<Derived> derived = new Thing<Derived>; 

如果你是这样做的:

 base = derived; 

你会得到一个错误,如“不能隐式转换…”

你有两个select,1)明确地转换它们,或2)告诉编译器隐式转换它们。

 base = (MyInterface<Base>)derived; // #1 

要么

 interface MyInterface<out T> // #2 { T MyFunction(int variable); } 

如果你的界面如下所示,第二种情况可以发挥作用:

 interface MyInterface<T> { int MyFunction(T variable); // T is now a parameter } 

再次将其与这两个function相关联

 public int DoSomething(Base variable) { // Do stuff } public int DoSomethingElse(Derived variable) { return DoSomething((Base)variable); } 

希望你看到情况如何逆转,但本质上是相同types的转换。

再次使用相同的类

 public class Base public class Derived : Base public class Thing<T>: MyInterface<T> { } 

和相同的对象

 MyInterface<Base> base = new Thing<Base>; MyInterface<Derived> derived = new Thing<Derived>; 

如果你试图设定他们平等

 base = derived; 

你的编译器会再次对你大喊,你有和以前一样的select

 base = (MyInterface<Base>)derived; 

要么

 interface MyInterface<in T> //changed { int MyFunction(T variable); // T is still a parameter } 

基本上用于generics只是用来作为接口方法的返回types。 用于何时将用作Method参数。 使用委托时也适用相同的规则。

有奇怪的例外,但我不会在这里担心他们。

对不起,提前有任何粗心的错误=)

协变很容易理解。 这很自然。 逆变更混乱。

仔细看一下MSDN的这个例子 。 看看SortedList如何期望一个IComparer,但是它们传入一个ShapeAreaComparer:IComparer。 形状是“更大”的types(它在被调用者的签名中,而不是调用者),但是反例允许ShapeAreaComparer中的“更小”types(圆形)被replace,而ShapeAreaComparer中的每个元素通常都是Shape。

希望有所帮助。

在进入主题之前,让我们快速回顾一下:

基类引用可以保存派生类对象,但反之亦然。

协方差 :协方差可以让你传递一个派生types的对象,其中一个基types的对象是预期协变可以应用在委托,generics,数组,接口等

逆变:逆变应用于参数。 它允许将具有基类参数的方法分配给期望派生类参数的委托

看看下面的简单例子:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CovarianceContravarianceDemo { //base class class A { } //derived class class B : A { } class Program { static A Method1(A a) { Console.WriteLine("Method1"); return new A(); } static A Method2(B b) { Console.WriteLine("Method2"); return new A(); } static B Method3(B b) { Console.WriteLine("Method3"); return new B(); } public delegate A MyDelegate(B b); static void Main(string[] args) { MyDelegate myDel = null; myDel = Method2;// normal assignment as per parameter and return type //Covariance, delegate expects a return type of base class //but we can still assign Method3 that returns derived type and //Thus, covariance allows you to assign a method to the delegate that has a less derived return type. myDel = Method3; A a = myDel(new B());//this will return a more derived type object which can be assigned to base class reference //Contravariane is applied to parameters. //Contravariance allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class. myDel = Method1; myDel(new B()); //Contravariance, } } } 

用约翰斯的话来说:

协方差允许在原始types仅用于“输出”位置(例如作为返回值)的API 中用 “较大”(较不具体的)types替代 。 反变换允许在原始types仅用于“input”位置的API中replace “更小”(更具体)的types。

我首先发现他的解释令人困惑,但是我认为一旦被replace是有意义的,结合C#编程指南中的例子:

 // Covariance. IEnumerable<string> strings = new List<string>(); // An object that is instantiated with a more derived type argument // is assigned to an object instantiated with a less derived type argument. // Assignment compatibility is preserved. IEnumerable<object> objects = strings; // Contravariance. // Assume that the following method is in the class: // static void SetObject(object o) { } Action<object> actObject = SetObject; // An object that is instantiated with a less derived type argument // is assigned to an object instantiated with a more derived type argument. // Assignment compatibility is reversed. Action<string> actString = actObject; 

转换器代表帮助我了解它:

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