C# 逆变解释
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1962629/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Contravariance explained
提问by Stan R.
First of, I have read many explanations on SO and blogs about covariance and contravariance and a big thanks goes out to Eric Lippertfor producing such a great series on Covariance and Contravariance.
首先,我已经阅读了许多关于 SO 的解释和关于协方差和逆变的博客,非常感谢Eric Lippert制作了如此出色的关于协方差和逆变的系列。
However I have a more specific question that I am trying to get my head around a little bit.
但是,我有一个更具体的问题,我想稍微了解一下。
As far as I understand per Eric's explanationis that Covariance and Contravariance are both adjectives that describe a transformation. Covariant transformation is that which preserves the order of types and Contravariant transformation is one that reverses it.
据我了解,根据Eric 的解释,协方差和逆变都是描述转换的形容词。协变变换是保留类型顺序的变换,而逆变变换是反转它的变换。
I understand covariance in such a manner that I think most developers understand intuitively.
我以我认为大多数开发人员直观地理解的方式来理解协方差。
//covariant operation
Animal someAnimal = new Giraffe();
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal();
The return operation here is covariant as we are preserving the size in which both Animal is still bigger than Mammal or Giraffe. On that note most return operations are covariant, contravariant operations would not make sense.
这里的返回操作是协变的,因为我们保留了 Animal 仍然大于 Mammal 或 Giraffe 的大小。在这一点上,大多数返回操作是协变的,逆变操作没有意义。
//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();
This piece of code of course would not make sense to most developers.
这段代码当然对大多数开发人员来说没有意义。
My confusion lies in Contravariant argument parameters. If you had a method such as
我的困惑在于逆变参数参数。如果你有一个方法,比如
bool Compare(Mammal mammal1, Mammal mammal2);
I have always learned that input parameters always force contravariant behavior. Such that if the type is used as an input parameter its behavior should be contravariant.
我一直都知道输入参数总是强制逆变行为。这样,如果该类型用作输入参数,则其行为应该是逆变的。
However what is the difference between the following code
但是下面的代码有什么区别
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?
By the same token that you can't do something like this you can't do
同样的道理,你不能做这样的事情,你不能做
//not valid
Mammal mammal1 = new Animal();
//not valid
Compare(new Animal(), new Dolphin());
I guess what I am asking is, what makes method argument passing a contravariant transformation.
我想我要问的是,是什么让方法参数通过逆变变换。
Sorry for the long post, maybe I am understand this incorrectly.
对不起,这篇长文章,也许我理解不正确。
EDIT:
编辑:
Per some conversation below, I understand that for instance using a delegate layer can clearly show contravariance. Consider the following example
根据下面的一些对话,我理解例如使用委托层可以清楚地显示逆变。考虑下面的例子
//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;
Of course this is illegal because someone can pass any Animal to someAction, where as the ProcessMammal expects anything thats Mammal or more specific ( lesser than Mammal ). Which is why someAction has to only be Action or anything more specific (Action)
当然,这是非法的,因为有人可以将任何 Animal 传递给 someAction,而 ProcessMammal 期望任何 Mammal 或更具体的东西(小于 Mammal )。这就是为什么 someAction 只能是 Action 或更具体的东西 (Action)
However this is introducing a layer of delegates in the middle, is it necessary that for a contravariant projection to happen there has to be a delegate in the middle? And if we were to define Process as an interface we would declare the argument parameter as a contravariant type only because we wouldn't want someone to be able to do what I had shown above with delegates?
然而,这在中间引入了一层代表,对于逆变投影是否有必要在中间有一个代表?如果我们将 Process 定义为接口,我们会将实参参数声明为逆变类型只是因为我们不希望有人能够使用委托执行我上面展示的操作?
public interface IProcess<out T>
{
void Process(T val);
}
采纳答案by Alexandra Rusina
Update:Ooops. As it turned out, I mixed up variance and "assignment compatibility" in my initial answer. Edited the answer accordingly. Also I wrote a blog post that I hope should answer such questions better: Covariance and Contravariance FAQ
更新:哎呀。事实证明,我在最初的回答中混淆了方差和“分配兼容性”。相应地编辑了答案。我还写了一篇博文,希望能更好地回答这些问题:Covariance and Contravariance FAQ
Answer:I guess the answer to your first question is that you don't have contravariance in this example:
答:我猜你的第一个问题的答案是你在这个例子中没有逆变:
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
Furthermore, you don't even have covariance here. What you have is called "assignment compatibility", which means that you can always assign an instance of a more derived type to an instance of a less derived type.
此外,您在这里甚至没有协方差。您所拥有的称为“分配兼容性”,这意味着您始终可以将派生程度更高的类型的实例分配给派生程度较低的类型的实例。
In C#, variance is supported for arrays, delegates, and generic interfaces. As Eric Lippert said in his blog post What's the difference between covariance and assignment compatibility?is that it's better to think about variance as "projection" of types.
在 C# 中,数组、委托和泛型接口支持变体。正如 Eric Lippert 在他的博客文章协方差和赋值兼容性之间有什么区别?最好将方差视为类型的“投影”。
Covariance is easier to understand, because it follows the assignment compatibility rules (array of a more derived type can be assigned to an array of a less derived type, "object[] objs = new string[10];"). Contravariance reverses these rules. For example, imagine that you could do something like "string[] strings = new object[10];". Of course, you can't do this because of obvious reasons. But that would be contravariance (but again, arrays are not contravariant, they support covariance only).
协方差更容易理解,因为它遵循赋值兼容性规则(派生程度更高的类型的数组可以分配给派生程度较低的类型的数组,“object[] objs = new string[10];”)。逆变颠倒了这些规则。例如,假设您可以执行诸如“string[] strings = new object[10];”之类的操作。当然,由于显而易见的原因,您不能这样做。但这将是逆变(但同样,数组不是逆变的,它们仅支持协变)。
Here are the examples from MSDN that I hope will show you what contravariance really means (I own these documents now, so if you think something is unclear in the docs, feel free to give me feedback):
以下是来自 MSDN 的示例,我希望它们可以向您展示逆变的真正含义(我现在拥有这些文档,因此如果您认为文档中的某些内容不清楚,请随时给我反馈):
Using Variance in Interfaces for Generic Collections
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());
// 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; }
Using Variance for Func and Action Generic Delegates
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;
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());
// 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; }
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;
Hope this helps.
希望这可以帮助。
回答by BlueMonkMN
(Edited in response to comments)
(根据评论编辑)
This MSDN article on the topicdescribed covariance and contravariance as it applies to matching a function to a delegate. A variable of the delegate type:
这篇关于该主题的 MSDN 文章描述了协变和逆变,因为它适用于将函数与委托进行匹配。委托类型的变量:
public delegate bool Compare(Giraffe giraffe, Dolphin dolphin);
could (because of contravariance) be populated with the function:
可以(因为逆变)用函数填充:
public bool Compare(Mammal mammal1, Mammal mammal2)
{
return String.Compare(mammal1.Name, mammal2.Name) == 0;
}
From my reading, it doesn't have to do with calling the function directly, but matching functions with delegates. I'm not sure that it can be boiled down to the level you demonstrate, with individual variables or object assignments being contravariant or covariant. But the assignment of a delegate uses contravariance or covariance in a way that makes sense to me according to the linked article. Because the delegate's signature contains more derived types than the actual instance, this is referred to as "contravariance", something separate from "covariance" in which a delegate's return type is less derived than the actual instance.
从我的阅读来看,它与直接调用函数无关,而是将函数与委托进行匹配。我不确定它是否可以归结为您演示的级别,单个变量或对象分配是逆变的或协变的。但是根据链接的文章,委托的分配以对我有意义的方式使用逆变或协变。因为委托的签名包含比实际实例更多的派生类型,这被称为“逆变”,与“协变”不同,其中委托的返回类型比实际实例派生的更少。
回答by Lee
My understanding is that it is not subtype relationships which are co/contra-variant but rather operations (or projections) between those types (such as delegates and generics). Therefore:
我的理解是,共变/逆变的不是子类型关系,而是这些类型(例如委托和泛型)之间的操作(或投影)。所以:
Animal someAnimal = new Giraffe();
is not co-variant, but rather this is just assignment compatibility since the type Giraffe is 'smaller than' the type Animal. Co/contra-variance becomes an issue when you have some projection between these types, such as:
不是协变的,而是因为 Giraffe 类型比 Animal 类型“小”,所以这只是赋值兼容性。当您在这些类型之间有一些投影时,协方差成为一个问题,例如:
IEnumerable<Giraffe> giraffes = new[] { new Giraffe() };
IEnumerable<Animal> animals = giraffes;
This is not valid in C#3, however it should be possible since a sequence of giraffes is a sequence of animals. The projection T -> IEnumerable<T>
preserves the 'direction' of the type relationship since Giraffe < Animal
and IEnumerable<Giraffe> < IEnumerable<Animal>
(note that assignment requires that the type of the left-hand side is at least as wide as the right).
这在 C#3 中无效,但它应该是可能的,因为长颈鹿序列是动物序列。投影T -> IEnumerable<T>
保留了类型关系的“方向”,因为Giraffe < Animal
和IEnumerable<Giraffe> < IEnumerable<Animal>
(请注意,赋值要求左侧的类型至少与右侧的类型一样宽)。
Contra-variance reverses the type relationship:
Contra-variance 反转了类型关系:
Action<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)};
Action<Giraffe> printGiraffe = printAnimal;
This is also not legal in C#3, but it should be since any action taking an animal can cope with being passed a Giraffe. However, since Giraffe < Animal
and Action<Animal> < Action<Giraffe>
the projection has reversed the type relationships. This is legal in C#4.
这在 C#3 中也是不合法的,但应该是这样,因为任何采取动物的行动都可以应对被传递给长颈鹿。然而,由于Giraffe < Animal
和Action<Animal> < Action<Giraffe>
投影已经颠倒了类型关系。这在 C#4 中是合法的。
So to answer the questions in your example:
因此,要回答示例中的问题:
//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());
回答by Dave O.
Covariance and Contravariance are not things you can observe when instancing classes. Thus it is wrong to speak about one of them when looking at a simple class instantiation, like in your example:
Animal someAnimal = new Giraffe();
//covariant operation
协变和逆变不是您在实例化类时可以观察到的。因此,在查看简单的类实例化时谈论其中之一是错误的,例如在您的示例中:
Animal someAnimal = new Giraffe();
//covariant operation
These terms do not classify operations. The terms Covariance, Contravariance and Invariance describe the relationship between certain aspectsof classes and their subclasses.
这些术语不对操作进行分类。术语协方差、逆变和不变性描述了类的某些方面与其子类之间的关系。
- Covariance
- means that an aspect changes similar to the direction of inheritance.
- Contravariance
- means that an aspect changes opposite to the direction of inheritance.
- Invariance
- means that an aspect does not change from a class to its sub class(es).
- 协方差
- 意味着一个方面的变化类似于继承的方向。
- 逆变
- 意味着一个方面的变化与继承方向相反。
- 不变性
- 意味着一个方面不会从一个类变为它的子类。
We generally regard the following aspects, when talking about Cov., Contrav. and Inv.:
谈到Cov.,Contrav,我们一般从以下几个方面考虑。和 Inv.:
- Methods
- Parameter types
- Return types
- Other signature related aspects like thrown exceptions.
- Generics
- 方法
- 参数类型
- 返回类型
- 其他与签名相关的方面,如抛出的异常。
- 泛型
Let us have a look at a few examples to get a better understanding of the terms.
让我们看几个例子来更好地理解这些术语。
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) }
在这两种情况下,“方法”都会被覆盖!此外,上面的例子是唯一的legal occurrences of Cov. and Contrav. in object oriented languagesCov. 的法律事件 和反对派。在面向对象的语言中。:- Covariance - Return types and exception throw statements
- Contravariance - Input parameters
- Invariance - Input and Output parameters
- 协方差 - 返回类型和异常抛出语句
- 逆变 - 输入参数
- 不变性 - 输入和输出参数
Let us have a look at some counter examples to better understand the above list:
让我们看一些反例来更好地理解上面的列表:
//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).
This topic is so sophisticated, that I could go on for a very long time. I advise you to check Cov. and Contrav. of Generics by yourself. Further, you need to know how dynamic binding works to fully understand the examples (which methods get exactly called).
这个话题太复杂了,我可以讲很长时间。我建议你检查Cov。和反对派。自己的泛型。此外,您需要了解动态绑定的工作原理以完全理解示例(哪些方法被准确调用)。
The terms arose from the Liskov substitution principle, which defines necessary criteria for modelling a data type as a sub type of another one. You might also want to investigate it.
这些术语源自 Liskov 替换原则,该原则定义了将数据类型建模为另一种数据类型的子类型的必要标准。您可能还想对其进行调查。
回答by arviman
Look at it this way: If I have a function func that deals with Subtype Mammal, of the form Mammal m = Func(g(Mammal)), I can swap out Mammal with something that encompassesMammal, which here is the Base Animal.
这样看:如果我有一个函数 func 处理子类型 Mammal,形式为Mammal m = Func(g(Mammal)),我可以用包含Mammal 的东西替换Mammal,这里是 Base Animal。
In terms of a sporting analogy to understand the below image, you can catch a ball with your bare hands like in Cricket, but it's also possible (and easier) to catch a ball using Baseball gloves.
就理解下图的体育类比而言,您可以像在板球比赛中那样徒手接球,但也可以(并且更容易)使用棒球手套接球。
What you see on the left is covariance, what you see inside the parameter part is contravariance.
你在左边看到的是协方差,你在参数部分看到的是逆变。
You may wonder "Why is the left green curve bigger than the red curve? Isn't the Subtype which usually does more than the basetype supposed to be bigger?" Answer: No. The size of the bracket denotes the variety of objects allowed, like a Venn diagram. A Set of Mammal is smaller than Set Animal. Similarly, f(Mammal) is smaller than f(Animal) as it only supports a smaller set of objects. (i.e a function that handles Mammals won't handle all Animals, but a function that handles Animals can always handle a Mammal). Hence, the relationship is inverted as f(animal) can be passed in instead of f(mammal) thereby making it contravariant.
你可能想知道“为什么左边的绿色曲线比红色曲线大?通常比基类型做得更多的子类型不是应该更大吗?” 答案:不可以。括号的大小表示允许的对象种类,如维恩图。一组哺乳动物小于一组动物。类似地,f(Mammal) 比 f(Animal) 小,因为它只支持较小的一组对象。(即处理哺乳动物的函数不会处理所有动物,但处理动物的函数总是可以处理哺乳动物)。因此,这种关系是颠倒的,因为可以传入 f(animal) 而不是 f(mammal) 从而使其逆变。