oxb*_*kes 18
协方差非常简单,从一些集合类的角度来看是最好的List
.我们可以使用某个类型参数来参数化List
类T
.也就是说,我们的列表包含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
派生自object
,string[]
但不派生自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)
Woman
Person
显然来自。现在考虑你有这两个功能:
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
是较大的比WorkWithWoman
。
WorkWithPerson
也被认为是较大的比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
不会错,对吧?
到现在为止,您可能想知道所有这些与泛型有何关系。答案是方差也可以应用于泛型。前面的例子使用了object
和string
数组。这里的代码使用通用列表而不是数组:
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 中,泛型中的差异支持已被清除,现在可以将新关键字in和out与泛型类型参数一起使用。它们可以定义和限制特定类型参数的数据流方向,从而允许差异起作用。但是在 的情况下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
。
在使用泛型类型时,重要的是要注意差异以及编译器应用各种技巧的方式,以使您的代码按您期望的方式工作。
关于方差的知识比本章所涵盖的要多,但这足以使所有进一步的代码易于理解。
参考:
以下是我们如何为C#4.0添加新的方差功能的文章.从底部开始.
http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
归档时间: |
|
查看次数: |
5758 次 |
最近记录: |