C# 协方差和逆变的区别

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2184551/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 00:10:35  来源:igfitidea点击:

Difference between Covariance & Contra-variance

c#c#-4.0covariancecontravariance

提问by jane doe

I am having trouble understanding the difference between covariance and contravariance.

我无法理解协方差和逆变之间的区别。

采纳答案by Eric Lippert

The question is "what is the difference between covariance and contravariance?"

问题是“协方差和逆变之间有什么区别?”

Covariance and contravariance are properties of a mapping function that associates one member of a set with another. More specifically, a mapping can be covariant or contravariant with respect to a relationon that set.

协方差和逆变是映射函数的属性,该函数将集合的一个成员与另一个相关联。更具体地说,映射对于该集合上的关系可以是协变的或逆变的。

Consider the following two subsets of the set of all C# types. First:

考虑所有 C# 类型集合的以下两个子集。第一的:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

And second, this clearly related set:

其次,这个明显相关的集合:

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }

There is a mappingoperation from the first set to the second set. That is, for each T in the first set, the correspondingtype in the second set is IEnumerable<T>. Or, in short form, the mapping is T → IE<T>. Notice that this is a "thin arrow".

从第一组到第二组有一个映射操作。也就是说,对于第一组中的每个 T,第二组中对应的类型是IEnumerable<T>。或者,简而言之,映射是T → IE<T>。请注意,这是一个“细箭头”。

With me so far?

陪我到此为止?

Now let's consider a relation. There is an assignment compatibility relationshipbetween pairs of types in the first set. A value of type Tigercan be assigned to a variable of type Animal, so these types are said to be "assignment compatible". Let's write "a value of type Xcan be assigned to a variable of type Y" in a shorter form: X ? Y. Notice that this is a "fat arrow".

现在让我们考虑一个关系。第一组中的类型对之间存在赋值兼容关系。type 的值Tiger可以分配给 type 的变量Animal,因此这些类型被称为“赋值兼容”。让我们写“类型的值X可以被分配给类型的变量Y”,在一个较短的形式:X ? Y。请注意,这是一个“粗箭头”。

So in our first subset, here are all the assignment compatibility relationships:

所以在我们的第一个子集中,这里是所有的分配兼容性关系:

Tiger  ? Tiger
Tiger  ? Animal
Animal ? Animal
Banana ? Banana
Banana ? Fruit
Fruit  ? Fruit

In C# 4, which supports covariant assignment compatibility of certain interfaces, there is an assignment compatibility relationship between pairs of types in the second set:

在支持某些接口的协变赋值兼容性的 C# 4 中,第二组中的类型对之间存在赋值兼容性关系:

IE<Tiger>  ? IE<Tiger>
IE<Tiger>  ? IE<Animal>
IE<Animal> ? IE<Animal>
IE<Banana> ? IE<Banana>
IE<Banana> ? IE<Fruit>
IE<Fruit>  ? IE<Fruit>

Notice that the mapping T → IE<T>preserves the existence and direction of assignment compatibility. That is, if X ? Y, then it is also true that IE<X> ? IE<Y>.

请注意,映射T → IE<T>保留了分配兼容性的存在性和方向。也就是说,如果X ? Y,那么 也成立IE<X> ? IE<Y>

If we have two things on either side of a fat arrow, then we can replace both sides with something on the right hand side of a corresponding thin arrow.

如果我们在粗箭头的两侧有两个东西,那么我们可以用相应的细箭头右侧的东西替换两侧。

A mapping which has this property with respect to a particular relation is called a "covariant mapping". This should make sense: a sequence of Tigers can be used where a sequence of Animals is needed, but the opposite is not true. A sequence of animals cannot necessarily be used where a sequence of Tigers is needed.

对于特定关系具有此属性的映射称为“协变映射”。这应该是有道理的:可以在需要一系列 Animals 的地方使用 Tigers 序列,但反之则不然。在需要老虎序列的地方不一定使用动物序列。

That's covariance. Now consider this subset of the set of all types:

这就是协方差。现在考虑所有类型集合的这个子集:

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }

now we have the mapping from the first set to the third set T → IC<T>.

现在我们有了从第一组到第三组的映射T → IC<T>

In C# 4:

在 C# 4 中:

IC<Tiger>  ? IC<Tiger>
IC<Animal> ? IC<Tiger>     Backwards!
IC<Animal> ? IC<Animal>
IC<Banana> ? IC<Banana>
IC<Fruit>  ? IC<Banana>     Backwards!
IC<Fruit>  ? IC<Fruit>

That is, the mapping T → IC<T>has preserved the existence but reversed the directionof assignment compatibility. That is, if X ? Y, then IC<X> ? IC<Y>.

也就是说,映射T → IC<T>已经保留了存在,但反转的方向分配的兼容性。也就是说,如果X ? Y,那么IC<X> ? IC<Y>

A mapping which preserves but reversesa relation is called a contravariantmapping.

其中A映射蜜饯但反转的关系被称为逆变映射。

Again, this should be clearly correct. A device which can compare two Animals can also compare two Tigers, but a device which can compare two Tigers cannot necessarily compare any two Animals.

同样,这应该是正确的。可以比较两只动物的设备也可以比较两只老虎,但是可以比较两只老虎的设备不一定可以比较任何两只动物。

So that's the difference between covariance and contravariance in C# 4. Covariance preservesthe direction of assignability. Contravariance reversesit.

这就是 C# 4 中协变和逆变之间的区别。协变保留了可分配性的方向。逆变反转

回答by Jon Skeet

It's probably easiest to give examples - that's certainly how I remember them.

举个例子可能是最容易的——这当然是我记住它们的方式。

Covariance

协方差

Canonical examples: IEnumerable<out T>, Func<out T>

典型的例子:IEnumerable<out T>Func<out T>

You can convert from IEnumerable<string>to IEnumerable<object>, or Func<string>to Func<object>. Values only come out fromthese objects.

您可以从 转换IEnumerable<string>IEnumerable<object>,或转换Func<string>Func<object>。值仅来自这些对象。

It works because if you're only taking values out of the API, and it's going to return something specific (like string), you can treat that returned value as a more general type (like object).

之所以有效,是因为如果您只从 API 中取出值,并且它将返回特定的内容(例如string),您可以将该返回值视为更通用的类型(例如object)。

Contravariance

逆变

Canonical examples: IComparer<in T>, Action<in T>

典型的例子:IComparer<in T>Action<in T>

You can convert from IComparer<object>to IComparer<string>, or Action<object>to Action<string>; values only go intothese objects.

您可以从转换IComparer<object>IComparer<string>,或Action<object>Action<string>; 值只进入这些对象。

This time it works because if the API is expecting something general (like object) you can give it something more specific (like string).

这次它起作用了,因为如果 API 期待一些通用的东西(比如object),你可以给它一些更具体的东西(比如string)。

More generally

更普遍

If you have an interface IFoo<T>it can be covariant in T(i.e. declare it as IFoo<out T>if Tis only used in an output position (e.g. a return type) within the interface. It can be contravariant in T(i.e. IFoo<in T>) if Tis only used in an input position (e.g. a parameter type).

如果你有一个接口,IFoo<T>它可以是协变的T(即声明它IFoo<out T>好像T只在接口内的输出位置(例如返回类型)中使用。它可以是逆变的T(即IFoo<in T>)如果T只用于输入位置(例如参数类型)。

It gets potentially confusing because "output position" isn't quite as simple as it sounds - a parameter of type Action<T>is still only using Tin an output position - the contravariance of Action<T>turns it round, if you see what I mean. It's an "output" in that the values can pass from the implementation of the method towardsthe caller's code, just like a return value can. Usually this sort of thing doesn't come up, fortunately :)

它可能会令人困惑,因为“输出位置”并不像听起来那么简单 - 一个类型的参数Action<T>仍然只T在输出位置使用 -Action<T>如果你明白我的意思,逆变器将它反过来。这是在该值可以从方法的实现通过“输出”来电者的代码,就像一个返回值可以。通常这种事情不会出现,幸运的是:)

回答by Nico

I hope my post helps to get a language-agnostic view of the topic.

我希望我的帖子有助于获得对该主题的语言不可知论观点。

For our internal trainings I have worked with the wonderful book "Smalltalk, Objects and Design (Chamond Liu)" and I rephrased following examples.

在我们的内部培训中,我使用了很棒的书“Smalltalk、对象和设计(Chamond Liu)”,并且我改写了以下示例。

What does “consistency” mean? The idea is to design type-safe type hierarchies with highly substitutable types. The key to get this consistency is sub type based conformance, if you work in a statically typed language. (We'll discuss the Liskov Substitution Principle (LSP) on a high level here.)

“一致性”是什么意思?这个想法是设计具有高度可替换类型的类型安全类型层次结构。如果您使用静态类型语言,那么获得这种一致性的关键是基于子类型的一致性。(我们将在这里高层次地讨论里氏替换原则 (LSP)。)

Practical examples (pseudo code/invalid in C#):

实际示例(伪代码/C# 中无效):

  • Covariance: Let's assume Birds that lay Eggs “consistently” with static typing: If the type Bird lays an Egg, wouldn't Bird's subtype lay a subtype of Egg? E.g. the type Duck lays a DuckEgg, then the consistency is given. Why is this consistent? Because in such an expression:Egg anEgg = aBird.Lay();the reference aBird could be legally substituted by a Bird or by a Duck instance. We say the return type is covariant to the type, in which Lay() is defined. A subtype's override may return a more specialized type. => “They deliver more.”

  • Contravariance: Let's assume Pianos that Pianists can play “consistently” with static typing: If a Pianist plays Piano, would she be able to play a GrandPiano? Wouldn't rather a Virtuoso play a GrandPiano? (Be warned; there is a twist!) This is inconsistent! Because in such an expression: aPiano.Play(aPianist);aPiano couldn't be legally substituted by a Piano or by a GrandPiano instance! A GrandPiano can only be played by a Virtuoso, Pianists are too general! GrandPianos must be playable by more general types, then the play is consistent. We say the parameter type is contravariant to the type, in which Play() is defined. A subtype's override may accept a more generalized type. => “They require less.”

  • 协方差:让我们假设 Birds 使用静态类型“一致地”下蛋:如果 Bird 类型下一个 Egg,那么 Bird 的子类型不会下一个 Egg 的子类型吗?例如,类型 Duck 下一个 DuckEgg,然后给出一致性。为什么这是一致的?因为在这样的表达式中:Egg anEgg = aBird.Lay();引用 aBird 可以合法地替换为 Bird 或 Duck 实例。我们说返回类型与定义了 Lay() 的类型是协变的。子类型的覆盖可能会返回更特殊的类型。=>“他们提供更多。”

  • 逆变:让我们假设钢琴家可以使用静态打字“一致地”演奏钢琴:如果钢琴家演奏钢琴,她能演奏三角钢琴吗?不是宁愿演奏家演奏三角钢琴吗?(请注意;有一个转折!)这是不一致的!因为在这样的表达中:aPiano.Play(aPianist);aPiano 不能被 Piano 或 GrandPiano 实例合法地替代!三角钢琴只能由演奏家演奏,钢琴家太一般了!GrandPianos 必须能被更一般的类型演奏,然后演奏是一致的。我们说参数类型与定义 Play() 的类型是逆变的。子类型的覆盖可以接受更通用的类型。=>“他们需要的更少。”

Back to C#:
Because C# is basically a statically typed language, the "locations" of a type's interface that should be co- or contravariant (e.g. parameters and return types), must be marked explicitly to guarantee a consistent usage/development of that type, to make the LSP work fine. In dynamically typed languages LSP consistency is typically not a problem, in other words you could completely get rid of co- and contravariant "markup" on .Net interfaces and delegates, if you only used the type dynamic in your types. - But this is not the best solution in C# (you shouldn't use dynamic in public interfaces).

回到 C#:
因为 C# 基本上是一种静态类型语言,类型接口的“位置”应该是协变或逆变的(例如参数和返回类型),必须显式标记以保证该类型的一致使用/开发, 使 LSP 正常工作。在动态类型语言中,LSP 一致性通常不是问题,换句话说,如果您只在类型中使用类型 dynamic,您可以完全摆脱 .Net 接口和委托上的协变和逆变“标记”。- 但这不是 C# 中的最佳解决方案(您不应该在公共接口中使用动态)。

Back to theory:
The described conformance (covariant return types/contravariant parameter types) is the theoretical ideal (supported by the languages Emerald and POOL-1). Some oop languages (e.g. Eiffel) decided to apply another type of consistency, esp. also covariant parameter types, because it better describes the reality than the theoretical ideal. In statically typed languages the desired consistency must often be achieved by application of design patterns like “double dispatching” and “visitor”. Other languages provide so-called “multiple dispatch” or multi methods (this is basically selecting function overloads at run time, e.g. with CLOS) or get the desired effect by using dynamic typing.

回到理论:
所描述的一致性(协变返回类型/逆变参数类型)是理论上的理想(由 Emerald 和 POOL-1 语言支持)。一些 oop 语言(例如 Eiffel)决定应用另一种类型的一致性,尤其是。也是协变参数类型,因为它比理论理想更好地描述了现实。在静态类型语言中,通常必须通过应用“双重调度”和“访问者”等设计模式来实现所需的一致性。其他语言提供所谓的“多分派”或多方法(这基本上是在运行时选择函数重载,例如使用 CLOS)或通过使用动态类型获得所需的效果。

回答by woggles

The converter delegate helps me to understand the difference.

转换器委托帮助我理解差异。

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

TOutputrepresents covariancewhere a method returns a more specific type.

TOutput表示协方差,其中方法返回更具体的类型

TInputrepresents contravariancewhere a method is passed a less specific type.

TInput表示方法传递一个不太具体的类型的逆变

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

回答by VadzimV

Co and Contra variance are pretty logical things. Language type system forces us to support real life logic. It's easy to understand by example.

Co 和 Contra 方差是非常合乎逻辑的事情。语言类型系统迫使我们支持现实生活中的逻辑。通过例子很容易理解。

Covariance

协方差

For instance you want to buy a flower and you have two flowers shop in your city: rose shop and daisy shop.

例如,您想买一朵花,而您所在的城市有两家花店:玫瑰花店和雏菊花店。

If you ask someone "where is the flowers shop?" and someone tells you where is rose shop, would it be okay? Yes, because rose is a flower, if you want to buy a flower you can buy a rose. The same applies if someone replied you with the address of the daisy shop.

如果你问别人“花店在哪里?” 有人告诉你玫瑰店在哪里,可以吗?是的,因为玫瑰是一朵花,如果你想买一朵花,你可以买一朵玫瑰。如果有人回复您菊花店的地址,这同样适用。

This is example of covariance: you are allowed to cast A<C>to A<B>, where Cis a subclass of B, if Aproduces generic values (returns as a result from the function). Covariance is about producers, that's why C# use keyword outfor covariance.

这是协方差的示例:您可以强制转换A<C>A<B>,其中C是 的子类B,如果A生成泛型值(作为函数的结果返回)。协方差是关于生产者的,这就是 C# 使用关键字out进行协方差的原因。

Types:

类型:

class Flower {  }
class Rose: Flower { }
class Daisy: Flower { }

interface FlowerShop<out T> where T: Flower {
    T getFlower();
}

class RoseShop: FlowerShop<Rose> {
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop: FlowerShop<Daisy> {
    public Daisy getFlower() {
        return new Daisy();
    }
}

Question is "where is the flower shop?", answer is "rose shop there":

问题是“花店在哪里?”,答案是“玫瑰店在那里”:

static FlowerShop<Flower> tellMeShopAddress() {
    return new RoseShop();
}

Contravariance

逆变

For instance you want to gift a flower to your girlfriend and your girlfrend likes any flowers. Can you consider her as a person who loves roses, or as a person who loves daisies? Yes, because if she loves any flower she would love both rose and daisy.

例如,你想送一朵花给你的女朋友,而你的女朋友喜欢任何花。你能把她当成一个爱玫瑰的人,还是一个爱雏菊的人?是的,因为如果她喜欢任何花,她就会喜欢玫瑰和雏菊。

This is an example of the contravariance: you're allowed to cast A<B>to A<C>, where Cis subclass of B, if Aconsumes generic value. Contravariance is about consumers, that's why C# use keyword infor contravariance.

这是逆变的一个例子:如果使用泛型值,您可以强制转换A<B>A<C>,其中C是 的子类。逆变是关于消费者的,这就是 C# 使用关键字进行逆变的原因。BAin

Types:

类型:

interface PrettyGirl<in TFavoriteFlower> where TFavoriteFlower: Flower {
    void takeGift(TFavoriteFlower flower);
}

class AnyFlowerLover: PrettyGirl<Flower> {
    public void takeGift(Flower flower) {
        Console.WriteLine("I like all flowers!");
    }
}

You're considering your girlfriend who loves any flower as someone who loves roses, and giving her a rose:

你把爱任何花的女朋友当成爱玫瑰的人,送她一朵玫瑰:

PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

Links

链接