编程语言中的协方差和逆变

xde*_*000 20 c# c++ java programming-languages covariance

谁能解释我,编程语言理论中协方差和逆变的概念?

oxb*_*kes 18

协方差非常简单,从一些集合类的角度来看是最好的List.我们可以使用某个类型参数来参数ListT.也就是说,我们的列表包含T某些类型的元素T.列表将是协变的,如果

S是T的子类型iff List [S]是List [T]的子类型

(我在哪里使用数学定义iff表示当且仅当.)

也就是说,a List[Apple] 是a List[Fruit].如果有一些例程接受a List[Fruit]作为参数,并且我有一个List[Apple],那么我可以将其作为有效参数传递.

def something(l: List[Fruit]) {
    l.add(new Pear())
}
Run Code Online (Sandbox Code Playgroud)

如果我们的集合类List是可变的,那么协方差是没有意义的,因为我们可以假设我们的例程可以添加一些其他水果(不是苹果),如上所述.因此,我们应该只希望不可变集合类是协变的!


Son*_*nül 11

协方差逆变之间存在区别。
粗略地说,如果一个操作保持类型的顺序,那么它就是协变的,如果它颠倒了这个顺序,那么它就是逆变的。

排序本身旨在将更通用的类型表示为比更具体的类型更大的类型。
下面是 C# 支持协方差的情况的一个示例。首先,这是一个对象数组:

object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;
Run Code Online (Sandbox Code Playgroud)

当然,可以在数组中插入不同的值,因为最终它们都来自System.Object.Net 框架。换句话说,System.Object是一个很一般或很大的类型。现在这里有一个支持协方差的地方:
将较小类型的值分配给较大类型的变量

string[] strings=new string[] { "one", "two", "three" };
objects=strings;
Run Code Online (Sandbox Code Playgroud)

类型为 的变量对象object[]可以存储实际上为类型的值string[]

想一想——在某种程度上,这正是你所期望的,但话又说回来,事实并非如此。毕竟,虽然string派生自objectstring[] 但不派生自object[]。本示例中对协方差的语言支持使赋值成为可能,在许多情况下您都会发现这种情况。方差是一种使语言更直观地工作的功能。

围绕这些主题的考虑非常复杂。例如,根据前面的代码,这里有两种会导致错误的场景。

// Runtime exception here - the array is still of type string[],
// ints can't be inserted
objects[2]=10;

// Compiler error here - covariance support in this scenario only
// covers reference types, and int is a value type
int[] ints=new int[] { 1, 2, 3 };
objects=ints;
Run Code Online (Sandbox Code Playgroud)

逆变工作的一个例子有点复杂。想象一下这两个类:

public partial class Person: IPerson {
    public Person() {
    }
}

public partial class Woman: Person {
    public Woman() {
    }
}
Run Code Online (Sandbox Code Playgroud)

WomanPerson显然来自。现在考虑你有这两个功能:

static void WorkWithPerson(Person person) {
}

static void WorkWithWoman(Woman woman) {
}
Run Code Online (Sandbox Code Playgroud)

其中一个函数用 a 做一些事情(无关紧要)Woman,另一个更通用,可以处理从Person. 在Woman事情方面,您现在还拥有这些:

delegate void AcceptWomanDelegate(Woman person);

static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
    acceptWoman(woman);
}
Run Code Online (Sandbox Code Playgroud)

DoWork是一个函数,它可以接受 aWoman和对也接受 a 的函数的引用Woman,然后它将 的实例传递Woman给委托。考虑您在此处拥有的元素的多态性Person较大的Woman,并且WorkWithPerson较大的WorkWithWomanWorkWithPerson也被认为是较大的AcceptWomanDelegate为方差的目的。

最后,你有这三行代码:

Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);
Run Code Online (Sandbox Code Playgroud)

Woman创建了一个实例。然后调用 DoWork,传入Woman实例以及对WorkWithWoman方法的引用。后者显然与委托类型兼容AcceptWomanDelegate——一个 type 参数Woman,没有返回类型。不过,第三行有点奇怪。该方法WorkWithPerson采用 aPerson作为参数,而不是Woman,如 所要求的那样AcceptWomanDelegate。尽管如此,WorkWithPerson与委托类型兼容。逆变使之成为可能,因此在委托的情况下,较大的类型WorkWithPerson可以存储在较小类型的变量中AcceptWomanDelegate。再一次,这是直观的事情:如果WorkWithPerson可以使用 any Person,传入 aWoman不会错,对吧?

到现在为止,您可能想知道所有这些与泛型有何关系。答案是方差也可以应用于泛型。前面的例子使用了objectstring数组。这里的代码使用通用列表而不是数组:

List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;
Run Code Online (Sandbox Code Playgroud)

如果您尝试一下,您会发现这在 C# 中不受支持。在 C# 4.0 版和 .Net framework 4.0 中,泛型中的差异支持已被清除,现在可以将新关键字inout与泛型类型参数一起使用。它们可以定义和限制特定类型参数的数据流方向,从而允许差异起作用。但是在 的情况下List<T>,类型的数据是T双向流动的——类型上有方法List<T>返回T值,其他方法接收这些值。

这些方向限制的重点是在有意义的地方允许变化,但要防止出现诸如前面数组示例中提到的运行时错误之类的问题。当类型参数被装饰正确地与出来,编译器可检查,并允许或禁止,它的方差在编译时。Microsoft 已经努力将这些关键字添加到 .Net 框架中的许多标准接口中,例如IEnumerable<T>

public interface IEnumerable<out T>: IEnumerable {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

对于这个接口,类型T对象的数据流是明确的:它们只能从这个接口支持的方法中检索,不能传递给它们。因此,可以构建一个类似于List<T>前面描述的尝试的示例,但使用IEnumerable<T>

IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;
Run Code Online (Sandbox Code Playgroud)

从 4.0 版开始,此代码对于 C# 编译器是可以接受的,因为IEnumerable<T>由于类型参数上的out说明符是协变的T

在使用泛型类型时,重要的是要注意差异以及编译器应用各种技巧的方式,以使您的代码按您期望的方式工作。

关于方差的知识比本章所涵盖的要多,但这足以使所有进一步的代码易于理解。

参考:


Eri*_*ert 7

以下是我们如何为C#4.0添加新的方差功能的文章.从底部开始.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx