反向解释

首先,我已经阅读了关于协议和反变换的SO和博客的许多解释,并且非常感谢Eric Lippert在协变和逆变方面创作了这样一个系列。

然而,我有一个更具体的问题,我正试图让我的头稍微有点。

据我所知, 埃里克的解释是,协变和逆变都是描述转换的形容词。 协变变换是保持types顺序的变换,逆变换是逆转变换的变换。

我以这样的方式理解协变性,我认为大多数开发人员直观地理解。

//covariant operation Animal someAnimal = new Giraffe(); //assume returns Mammal, also covariant operation someAnimal = Mammal.GetSomeMammal(); 

这里的返回操作是协变的,因为我们正在保持动物比哺乳动物或长颈鹿更大的尺寸。 在这一点上,大多数返回操作是协变的,逆变操作是没有意义的。

  //if return operations were contravariant //the following would be illegal //as Mammal would need to be stored in something //equal to or less derived than Mammal //which would mean that Animal is now less than or equal than Mammal //therefore reversing the relationship Animal someAnimal = Mammal.GetSomeMammal(); 

这段代码当然对大多数开发者来说是没有意义的。

我的困惑在于反变换参数。 如果你有一个方法如

 bool Compare(Mammal mammal1, Mammal mammal2); 

我总是知道input参数总是强制逆变行为。 这样,如果types被用作input参数,它的行为应该是逆变的。

然而下面的代码有什么区别

 Mammal mammal1 = new Giraffe(); //covariant Mammal mammal2 = new Dolphin(); //covariant Compare(mammal1, mammal2); //covariant or contravariant? //or Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? 

同样的道理,你不能这样做,你不能这样做

  //not valid Mammal mammal1 = new Animal(); //not valid Compare(new Animal(), new Dolphin()); 

我想我正在问的是什么让方法论点通过一个逆变。

对不起,很长的职位,也许我不明白这个错误。

编辑:

通过下面的一些对话,我明白,例如使用委托图层可以清楚地显示反转。 考虑下面的例子

 //legal, covariance Mammal someMammal = new Mammal(); Animal someAnimal = someMammal; // legal in C# 4.0, covariance (because defined in Interface) IEnumerable<Mammal> mammalList = Enumerable.Empty<Mammal>(); IEnumerable<Animal> animalList = mammalList; //because of this, one would assume //that the following line is legal as well void ProcessMammal(Mammal someMammal); Action<Mammal> processMethod = ProcessMammal; Action<Animal> someAction = processMethod; 

当然,这是非法的,因为有人可以将任何动物传递给某种行动,因为ProcessMammal期望任何哺乳动物或更具体(比哺乳动物)更小的动物。 这就是为什么someAction只能是Action或更具体的(Action)

然而,这是在中间引入了一层代表,是否有必要有一个代表中间的代表? 如果我们将Process定义为一个接口,那么我们只会将参数参数声明为逆变types,这是因为我们不希望有人能够用代表来完成上面显示的内容。

 public interface IProcess<out T> { void Process(T val); } 

更新: Ooops。 事实certificate,我在最初的答案中混合了方差和“赋值兼容性”。 相应地编辑了答案。 我还写了一篇博文,希望能更好地回答这样的问题: 协方差和逆变常见问题解答

答案:我想你的第一个问题的答案是你在这个例子中没有反转:

 bool Compare(Mammal mammal1, Mammal mammal2); Mammal mammal1 = new Giraffe(); //covariant - no Mammal mammal2 = new Dolphin(); //covariant - no Compare(mammal1, mammal2); //covariant or contravariant? - neither //or Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? - neither 

而且,这里甚至没有协变。 你有什么叫做“赋值兼容性”,这意味着你总是可以将一个更多派生types的实例赋值给派生types较小的实例。

在C#中,数组,代理和通用接口支持方差。 正如Eric Lippert在他的博客中所说的, 协变性和赋值兼容性有什么区别? 最好是把方差看作types的“投影”。

协变性更容易理解,因为它遵循赋值兼容性规则(可以将更多派生types的数组分配给派生types较less的数组,“object [] objs = new string [10];”)。 反变换颠覆了这些规则。 例如,假设你可以做一些像“string [] strings = new object [10];”的东西。 当然,由于显而易见的原因你不能这样做。 但是那将是矛盾的(但是arrays不是逆变的,它们只支持协方差)。

这里是MSDN的例子,我希望能告诉你什么是逆向的真正意义(我现在拥有这些文档,所以如果你认为文档中有什么不清楚的地方,请随时给我反馈):

  1. 在通用集合的接口中使用差异

     Employee[] employees = new Employee[3]; // You can pass PersonComparer, // which implements IEqualityComparer<Person>, // although the method expects IEqualityComparer<Employee>. IEnumerable<Employee> noduplicates = employees.Distinct<Employee>(new PersonComparer()); 
  2. 在代表中使用差异

     // Event hander that accepts a parameter of the EventArgs type. private void MultiHandler(object sender, System.EventArgs e) { label1.Text = System.DateTime.Now.ToString(); } public Form1() { InitializeComponent(); // You can use a method that has an EventArgs parameter, // although the event expects the KeyEventArgs parameter. this.button1.KeyDown += this.MultiHandler; // You can use the same method // for an event that expects the MouseEventArgs parameter. this.button1.MouseClick += this.MultiHandler; } 
  3. 为Func和Actiongenerics代表使用差异

      static void AddToContacts(Person person) { // This method adds a Person object // to a contact list. } // The Action delegate expects // a method that has an Employee parameter, // but you can assign it a method that has a Person parameter // because Employee derives from Person. Action<Employee> addEmployeeToContacts = AddToContacts; 

希望这可以帮助。

我的理解是,这不是共同/反对变体的子types关系,而是这些types(如代表和generics)之间的操作(或预测)。 因此:

 Animal someAnimal = new Giraffe(); 

不是共同的变体,而是这只是赋值兼容性,因为types长颈鹿是“小于”types的动物。 如果您在这些types之间有一些投影,那么Co / Contra-variance会成为一个问题,例如:

 IEnumerable<Giraffe> giraffes = new[] { new Giraffe(); }; IEnumerable<Animal> animals = giraffes; 

这在C#3中是无效的,但它应该是可能的,因为长颈鹿序列是一系列动物。 从Giraffe < AnimalIEnumerable<Giraffe> < IEnumerable<Animal> (因为赋值要求左边的types至less是广泛的权利)。

反方差反转types关系:

 Action<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)}; Action<Giraffe> printGiraffe = printAnimal; 

这在C#3中也是不合法的,但应该是因为任何采取动物行为都可以应付被传递给长颈鹿。 然而,由于Giraffe < AnimalAction<Animal> < Action<Giraffe>的预测扭转了types的关系。 这在C#4中是合法的。

所以要回答你的例子中的问题:

 //the following are neither covariant or contravariant - since there is no projection this is just assignment compatibility Mammal mammal1 = new Giraffe(); Mammal mammal2 = new Dolphin(); //compare is contravariant with respect to its arguments - //the delegate assignment is legal in C#4 but not in C#3 Func<Mammal, Mammal, bool> compare = (m1, m2) => //whatever Func<Giraffe, Dolphin, bool> c2 = compare; //always invalid - right hand side must be smaller or equal to left hand side Mammal mammal1 = new Animal(); //not valid for same reason - animal cannot be assigned to Mammal Compare(new Animal(), new Dolphin()); 

协变和逆变并不是在实例化类时可以观察到的事情。 因此,当看一个简单的类实例时,就像在你的例子中谈论其中的一个是错误的: Animal someAnimal = new Giraffe(); //covariant operation Animal someAnimal = new Giraffe(); //covariant operation

这些术语不分类操作。 术语“协变”,“逆变”和“不变”描述了类和它们的子类的某些方面之间的关系。

协方差
意味着一个方面的变化类似于inheritance的方向。
逆变
意味着一个方面与inheritance的方向相反。
不变性
意味着一个方面不会从一个类改变到它的子类(es)。

在谈到Cov。,Contrav时,我们通常会考虑以下几个方面。 和Inv .:

  • 方法
    • 参数types
    • 返回types
    • 其他签名相关的方面,如抛出的exception。
  • generics

让我们看看几个例子来更好地理解这些术语。

 class T class T2 extends T //Covariance: The return types of the method "method" have the same //direction of inheritance as the classes A and B. class A { T method() } class B extends A { T2 method() } //Contravariance: The parameter types of the method "method" have a //direction of inheritance opposite to the one of the classes A and B. class A { method(T2 t) } class B { method(T t) } 

在这两种情况下,“方法”都被覆盖! 此外,上述例子是Cov的唯一合法事件。 和Contrav。 在面向对象的语言中

  • 协变 – 返回types和exception抛出语句
  • 逆变 – input参数
  • 不变性 – input和输出参数

让我们看看一些反例的例子,以更好地理解上面的列表:

 //Covariance of return types: OK class Monkey { Monkey clone() } class Human extends Monkey { Human clone() } Monkey m = new Human(); Monkey m2 = m.clone(); //You get a Human instance, which is ok, //since a Human is-a Monkey. //Contravariance of return types: NOT OK class Fruit class Orange extends Fruit class KitchenRobot { Orange make() } class Mixer extends KitchenRobot { Fruit make() } KitchenRobot kr = new Mixer(); Orange o = kr.make(); //Orange expected, but got a fruit (too general!) //Contravariance of parameter types: OK class Food class FastFood extends Food class Person { eat(FastFood food) } class FatPerson extends Person { eat(Food food) } Person p = new FatPerson(); p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats. //Covariance of parameter types: NOT OK class Person { eat(Food food) } class FatPerson extends Person { eat(FastFood food) } Person p = new FatPerson(); p.eat(new Food()); //Oops! FastFood expected, but got Food (too general). 

这个话题非常复杂,我可以持续很长时间。 我build议你检查Cov。 和Contrav。 generics的自己。 此外,你需要知道dynamic绑定是如何工作的,以充分理解这些例子(哪些方法可以被正确调用)。

这些术语来自Liskov替代原则,它定义了将数据typesbuild模为另一个子types的必要条件。 你可能也想调查一下。

(根据意见编辑)

这个MSDN文章的主题描述了协变和反变换,因为它适用于匹配一个函数与委托。 委托types的variables:

 public delegate bool Compare(Giraffe giraffe, Dolphin dolphin); 

可以(因为相反)填充的function:

 public bool Compare(Mammal mammal1, Mammal mammal2) { return String.Compare(mammal1.Name, mammal2.Name) == 0; } 

从我的阅读,它不需要直接调用函数,而是与代表匹配函数。 我不确定它是否可以归结为您所展示的级别,个体variables或对象分配是逆变还是协变。 但是,委托人的任务使用的是协变性或协变性的方式,对于我来说,根据链接的文章是有意义的。 因为委托的签名包含比实际实例更多的派生types,所以这被称为“逆变”,这与“协方差”是分开的,委托的返回types比实际实例less。

这样看:如果我有一个函数func处理哺乳动物的子types,其forms为Mammal m = Func(g(Mammal)) ,我可以用包含 Mammal的哺乳动物 (这里是基础动物 来replace哺乳动物。

就体育类比而言,要理解下面的图像,你可以像蟋蟀一样用双手抓住一个球,但是用棒球手套抓球也是可能的(也更容易)。

你在左边看到的是协方差,你在参数部分看到的是逆变。

在这里输入图像说明

你可能会想:“为什么左边的绿色曲线比红色曲线更大?是不是通常比基本types更大的子types? 答案:否。括号的大小表示允许的对象的种类,如维恩图。 一套哺乳动物比集动物小。 同样,f(Mammal)小于f(Animal),因为它只支持一小组对象。 (即处理哺乳动物的函数不会处理所有的动物,但是处理动物的函数总是可以处理哺乳动物)。 因此,这种关系是相反的,因为f(动物)可以通过而不是f(哺乳动物),从而使其逆变。