简体   繁体   English

C#中的协方差有哪些? (或者,协方差:例如)

[英]What are the kinds of covariance in C#? (Or, covariance: by example)

Covariance is (roughly) the ability to mirror inheritance of "simple" types in complex types that use them. 协方差(大致)是在使用它们的复杂类型中镜像 “简单”类型的继承的能力。
Eg We can always treat an instance of Cat as an instance of Animal . 例如,我们总是可以将Cat的实例视为Animal一个实例。 A ComplexType<Cat> may be treated as a ComplexType<Animal> , if ComplexType is covariant. 如果ComplexType是协变的,则ComplexType<Cat>可以被视为ComplexType<Animal>

I'm wondering: what are the "types" of covariance, and how do they relate to C# (are they supported?) 我想知道:协方差的“类型”是什么,它们如何与C#相关(它们是否受支持?)
Code examples would be helpful. 代码示例会很有帮助。

For instance, one type is return type covariance , supported by Java, but not C#. 例如,一种类型是返回类型协方差 ,由Java支持,但不支持C#。

I'm hoping someone with functional programming chops can chime in, too! 我希望有功能编程印章的人也可以加入!

Here's what I can think of: 这是我能想到的:

Update 更新

After reading the constructive comments and the ton of articles pointed (and written) by Eric Lippert, I improved the answer: 在阅读了Eric Lippert提出的建设性意见和大量文章之后,我改进了答案:

  • Updated the broken-ness of array covariance 更新了数组协方差的破坏性
  • Added "pure" delegate variance 添加了“纯”代理方差
  • Added more examples from the BCL 从BCL添加了更多示例
  • Added links to articles that explain the concepts in-depth. 添加了指向深入解释概念的文章的链接。
  • Added a whole new section on higher-order function parameter covariance. 添加了关于高阶函数参数协方差的全新部分。

Return type covariance : 返回类型协方差

Available in Java (>= 5) [1] and C++ [2] , not supported in C# (Eric Lippert explains why not and what you can do about it ): 可用Java(> = 5) [1]和C ++ [2] ,C#不支持(Eric Lippert解释了为什么不能以及你可以做些什么 ):

class B {
    B Clone();
}

class D: B {
    D Clone();
}

Interface covariance [3] - supported in C# 接口协方差 [3] - 在C#中受支持

The BCL defines the generic IEnumerable interface to be covariant: BCL将通用IEnumerable接口定义为协变:

IEnumerable<out T> {...}

Thus the following example is valid: 因此以下示例有效:

class Animal {}
class Cat : Animal {}

IEnumerable<Cat> cats = ...
IEnumerable<Animal> animals = cats;

Note that an IEnumerable is by definition "read-only" - you can't add elements to it. 请注意,根据定义, IEnumerable是“只读” - 您无法向其添加元素。
Contrast that to the definition of IList<T> which can be modified eg using .Add() : IList<T>的定义形成对比,可以使用.Add()进行修改:

public interface IEnumerable<out T> : ...  //covariant - notice the 'out' keyword
public interface IList<T> : ...            //invariant

Delegate covariance by means of method groups [4] - supported in C# 通过方法组[4] 委派协方差 - 在C#中支持

class Animal {}
class Cat : Animal {}

class Prog {
    public delegate Animal AnimalHandler();

    public static Animal GetAnimal(){...}
    public static Cat GetCat(){...}

    AnimalHandler animalHandler = GetAnimal;
    AnimalHandler catHandler = GetCat;        //covariance

}

"Pure" delegate covariance [5 - pre-variance-release article] - supported in C# “纯粹的”代表协方差 [5 - pre-variance-release article] - 在C#中得到支持

The BCL definition of a delegate that takes no parameters and returns something is covariant: 不带参数并返回内容的委托的BCL定义是协变的:

public delegate TResult Func<out TResult>()

This allows the following: 这允许以下内容:

Func<Cat> getCat = () => new Cat();
Func<Animal> getAnimal = getCat; 

Array covariance - supported in C#, in a broken way [6] [7] 数组协方差 - 在C# 中以破碎的方式支持 [6] [7]

string[] strArray = new[] {"aa", "bb"};

object[] objArray = strArray;    //covariance: so far, so good
//objArray really is an "alias" for strArray (or a pointer, if you wish)


//i can haz cat?
object cat == new Cat();         //a real cat would object to being... objectified.

//now assign it
objArray[1] = cat                //crash, boom, bang
                                 //throws ArrayTypeMismatchException

And finally - the surprising and somewhat mind-bending 最后 - 令人惊讶且有点令人费解
Delegate parameter covariance (yes, that's co -variance) - for higher-order functions. 委托参数协方差 (是的,那是方差) - 用于高阶函数。 [8]

The BCL definition of the delegate that takes one parameter and returns nothing is contravariant : 带有一个参数并且不返回任何内容的委托的BCL定义是逆变的

public delegate void Action<in T>(T obj)

Bear with me. 忍受我。 Let's define a circus animal trainer - he can be told how to train an animal (by giving him an Action that works with that animal). 让我们定义一个马戏团的动物训练师 - 他可以被告知如何训练动物(通过给他一个与该动物一起工作的Action )。

delegate void Trainer<out T>(Action<T> trainingAction);

We have the trainer definition, let's get a trainer and put him to work. 我们有培训师的定义,让我们找一个培训师并让他去工作。

Trainer<Cat> catTrainer = (catAction) => catAction(new Cat());

Trainer<Animal> animalTrainer = catTrainer;  
// covariant: Animal > Cat => Trainer<Animal> > Trainer<Cat> 

//define a default training method
Action<Animal> trainAnimal = (animal) => 
   { 
   Console.WriteLine("Training " + animal.GetType().Name + " to ignore you... done!"); 
   };

//work it!
animalTrainer(trainAnimal);

The output proves that this works: 输出证明这有效:

Training Cat to ignore you... done! 训练猫忽略你......完成!

In order to understand this, a joke is in order. 为了理解这一点,一个笑话是有序的。

A linguistics professor was lecturing to his class one day. 有一天,一位语言学教授正在为他的班级讲课。
"In English," he said, "a double negative forms a positive. “在英语中,”他说,“双重否定形成了积极的一面。
However," he pointed out, "there is no language wherein a double positive can form a negative." 然而,“他指出,”没有一种语言,其中双阳性可以形成负面。“

A voice from the back of the room piped up, "Yeah, right." 房间后面传来一个声音,“是的,没错。”

What's that got to do with covariance?! 与协方差有什么关系?!

Let me attempt a back-of-the-napkin demonstration. 让我尝试一下背面的演示。

An Action<T> is contravariant, ie it "flips" the types' relationship: Action<T>是逆变的,即它“翻转”类型的关系:

A < B => Action<A> > Action<B> (1)

Change A and B above with Action<A> and Action<B> and get: 使用Action<A>Action<B>更改上面的AB并获取:

Action<A> < Action<B> => Action<Action<A>> > Action<Action<B>>  

or (flip both relationships)

Action<A> > Action<B> => Action<Action<A>> < Action<Action<B>> (2)     

Put (1) and (2) together and we have: 将(1)和(2)放在一起,我们有:

,-------------(1)--------------.
 A < B => Action<A> > Action<B> => Action<Action<A>> < Action<Action<B>> (4)
         `-------------------------------(2)----------------------------'

But our Trainer<T> delegate is effectively an Action<Action<T>> : 但我们的Trainer<T>委托实际上是一个Action<Action<T>>

Trainer<T> == Action<Action<T>> (3)

So we can rewrite (4) as: 所以我们可以将(4)重写为:

A < B => ... => Trainer<A> < Trainer<B> 

- which, by definition, means Trainer is covariant. - 根据定义,这意味着培训师是协变的。

In short, applying Action twice we get contra-contra-variance, ie the relationship between types is flipped twice (see (4) ), so we're back to covariance. 简而言之,应用Action 两次得到反对方差,即类型之间的关系翻转两次 (见(4)),所以我们回到协方差。

This is best explained in terms of more generic, structural types. 最好用更通用的结构类型来解释。 Consider: 考虑:

  1. Tuple types: (T1, T2), a pair of types T1 and T2 (or more generally, n-tuples); 元组类型:(T1,T2),一对类型T1和T2(或更一般地,n元组);
  2. Function types: T1 -> T2, a function with argument type T1 and result T2; 函数类型:T1 - > T2,参数类型为T1且结果为T2的函数;
  3. Mutable types: Mut(T), a mutable variable holding a T. 可变类型:Mut(T),一个持有T的可变变量。

Tuples are covariant in both their component types, ie (T1, T2) < (U1, U2) iff T1 < U1 and T2 < U2 (where '<' means is-subtype-of). 元组在它们的组件类型中是协变的,即(T1,T2)<(U1,U2)iff T1 <U1和T2 <U2(其中'<'表示is-subtype-of)。

Functions are covariant in their result and contravariant in their argument, ie (T1 -> T2) < (U1 -> U2) iff U1 < T1 and T2 < U2. 函数在它们的结果中是协变的,在它们的参数中是逆变的,即(T1 - > T2)<(U1 - > U2)iff U1 <T1和T2 <U2。

Mutable types are invariant, ie Mut(T) < Mut(U) only iff T = U. 可变类型是不变的,即只有当T = U时Mut(T)<Mut(U)。

All these rules are the most general correct subtyping rules. 所有这些规则都是最通用的正确子类型规则。

Now, an object or interface type like you know it from mainstream languages can be interpreted as a fancy form of tuple containing its methods as functions, among other things. 现在,像主流语言中所知的对象或接口类型可以被解释为包含其作为函数的方法的元组的奇特形式。 For example, the interface 例如,界面

interface C<T, U, V> {
  T f(U, U)
  Int g(U)
  Mut(V) x
}

essentially represents the type 基本上代表了类型

C(T, U, V) = ((U, U) -> T, U -> Int, Mut(V))

where f, g, and x correspond to the 1st, 2nd, and 3rd component of the tuple, respectively. 其中f,g和x分别对应于元组的第一,第二和第三分量。

It follows from the rules above that C(T, U, V) < C(T', U', V') iff T < T' and U' < U and V = V'. 从上述规则可以得出,C(T,U,V)<C(T',U',V')iff T <T'且U'<U且V = V'。 That means that the generic type C is covariant in T, contravariant in U and invariant in V. 这意味着泛型类型C在T中是协变的,在U中是逆变的,在V中是不变的。

Another example: 另一个例子:

interface D<T> {
  Int f(T)
  T g(Int)
}

is

D(T) = (T -> Int, Int -> T)

Here, D(T) < D(T') only if T < T' and T' < T. In general, that can only be the case if T = T', so D actually is invariant in T. 这里,D(T)<D(T')仅在T <T'和T'<T时。一般情况下,只有在T = T'的情况下,所以D实际上在T中是不变的。

There also is a fourth case, sometimes called "bivariance", which means both co- and contravariant at the same time. 还有第四种情况,有时称为“双变量”,它同时意味着共变和反变。 For example, 例如,

interface E<T> { Int f(Int) }

is bivariant in T, because it is not actually used. 在T中是双变量的,因为它实际上并未使用。

Java employs the concept of use-site variance for generic types: the needed variance is specified at each use site. Java对通用类型使用了使用站点方差的概念:在每个使用站点指定所需的方差。 This is why Java programmers are required to be familiar with the so-called PECS rule. 这就是为什么Java程序员需要熟悉所谓的PECS规则的原因。 Yes, it is unwieldy and has already received plenty of criticism. 是的,它很笨拙,已经受到了很多批评。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM