仍然困惑协变和逆变和进/出
好的,我读了一下关于这个话题在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
关键字提供,这意味着使用out
types参数的派生类的genericstypes是OK的。 于是
IEnumerable<Fruit> fruit = new List<Apple>();
由于Apple
是一种Fruit
, List<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>
),是有道理的,因为任何带Fruit
function,也可以拿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();