任何人都可以解释我,在编程语言理论协变和逆变的概念?
Answer 1:
协方差是非常简单和一些集合类的角度来看的最好的思想List
。 我们可以参数化List
某种类型的参数类T
。 也就是说,我们的名单中包含类型的元素T
一些T
。 清单将协变,如果
S是当且仅当Ť列表[S]的子类型是列表的一个亚型[T]
(这里我使用的数学定义IFF意味着当且仅当 。)
也就是说,一个List[Apple]
是一个 List[Fruit]
。 如果有一些程序,它接受一个List[Fruit]
作为参数,我有一个List[Apple]
,那么我可以通过这个作为一个有效的参数。
def something(l: List[Fruit]) {
l.add(new Pear())
}
如果我们的集合类List
是可变的,那么协方差是没有意义的,因为我们可能会认为我们的程序可以添加一些其他的水果(这是不是一个苹果)同上。 因此,我们应该只喜欢一成不变的集合类进行协变!
Answer 2:
逆变为我们其余的人
Answer 3:
下面是我对我们如何增加新的变化特点,以C#4.0的文章。 从底层做起。
http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
Answer 4:
为了更加方便,这里有一个链接到所有埃里克利珀的方差物品的有序列表:
- 协变和逆变在C#中,第一部分
- 协变和逆变在C#中,两个部分:数组协变
- 协变和逆变在C#中,第三部分:方法组转换差异
- 协变和逆变在C#中,第四部分:真正代表方差
- 协变和逆变在C#中,第五部分:高阶函数伤害了我的大脑
- 协变和逆变在C#中,第六部分:接口方差
- 协变和逆变在C#中第七部分:为什么我们需要一个语法呢?
- 协变和逆变在C#中,八部分:语法选项
- 协变和逆变在C#中,九部分:重大更改
- 协变和逆变在C#中,部分十:处理歧义
Answer 5:
有协方差和逆变之间进行区分。
大致来说,操作是协变的,如果它保留的类型排序,以及逆变如果它反转这个顺序。
在本身订购是代表更普遍的类型,超过特定类型的大。
这里给出了C#支持协方差的情形的一个例子。 首先,这是对象的数组:
object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;
当然,可以将不同的值插入到所述阵列,因为在端它们都从派生System.Object
在.NET框架。 换句话说, System.Object
是一个非常普遍的或大的类型。 现在,这里的地方是支持协方差点:
分配一个较小的类型的值到更大类型的变量
string[] strings=new string[] { "one", "two", "three" };
objects=strings;
可变对象,这是类型的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;
用于逆变的工作的一个例子是有点复杂。 想象一下,这两个类:
public partial class Person: IPerson {
public Person() {
}
}
public partial class Woman: Person {
public Woman() {
}
}
Woman
来源于Person
,很明显。 假如您现在有这两个功能:
static void WorkWithPerson(Person person) {
}
static void WorkWithWoman(Woman woman) {
}
其中一个功能做一些事情(这并不重要),有Woman
,另一种是更通用的,可与源自任何类型的工作Person
。 在Woman
身边的事情,你现在也有这些:
delegate void AcceptWomanDelegate(Woman person);
static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
acceptWoman(woman);
}
DoWork
是,可以采取一个功能Woman
和一个函数,也需要一个参考Woman
,和然后将其传递的实例Woman
到委托。 想想你在这里元素的多态性 。 Person
大于 Woman
,并WorkWithPerson
大于 WorkWithWoman
。 WorkWithPerson
也比被认为较大 AcceptWomanDelegate
方差的目的。
最后,你有这些三行代码:
Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);
一个Woman
实例被创建。 然后DoWork的被调用时,在经过的Woman
实例以及到一个参考WorkWithWoman
方法。 后者是与委托类型兼容明显AcceptWomanDelegate
-类型的一个参数Woman
,没有返回类型。 第三行是有点奇怪,但。 该方法WorkWithPerson
需要一个Person
作为参数,而不是一个Woman
所要求的AcceptWomanDelegate
。 然而, WorkWithPerson
与委托类型兼容。 逆变使得有可能,所以在代表的情况下,较大型WorkWithPerson
可被存储在较小的类型的变量AcceptWomanDelegate
。 一旦更多的是直观的事情: 如果WorkWithPerson
可以与任何工作的Person
,在路过的Woman
都不能错 ,对吧?
现在,你可能想知道这一切是如何涉及到仿制药。 答案是方差可以适用于仿制药为好。 前述实施例中使用object
和string
阵列。 这里的代码使用泛型列表,而不是数组:
List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;
如果你尝试了这一点,你会发现,这是不是在C#中的支持方案。 在C#4.0版以及框架4.0,在仿制药方差支持已被清理,而现在可以进出使用新的关键字泛型类型参数。 它们可以定义和限制数据流的方向为特定类型的参数,允许方差工作。 但是,在的情况下, List<T>
类型的数据T
在两个方向流动-有对类型的方法List<T>
返回T
值,和其他人接收这样的值。
这些方向性限制的点是允许方差在有意义 ,但要防止像以前的阵列的例子之一提到的运行时错误的问题 。 当类型参数被装饰正确地与或 缩小 ,编译器可以在编译时检查,并允许或禁止,它的方差。 微软已经向.NET Framework中添加这些关键字的许多标准接口的努力,像IEnumerable<T>
public interface IEnumerable<out T>: IEnumerable {
// ...
}
对于这个接口,类型的数据流T
的对象是明确的: 他们永远只能从这个接口,而不是传递到他们支持的方法来获得 。 其结果是,有可能构建类似的示例List<T>
尝试先前所描述的,但使用IEnumerable<T>
IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;
这个代码是因为4.0版本,因为可以接受的C#编译器IEnumerable<T>
是协变由于对类型参数的出说明符T
。
当使用泛型类型的工作,重要的是知道方差和编译器,采用各种欺骗,以使你的代码工作,你期望它的方式方法是非常重要的。
还有更多的了解余额高于覆盖在本章中,但这应足以使所有进一步的代码可以理解的。
参考:
PROFESSIONAL Functional Programming in C#
Answer 6:
巴特迪斯拥有约协方差和逆变一个伟大的博客条目在这里 。
Answer 7:
C#和CLR的结合到委托的方法时允许协方差和引用类型的禁忌方差。 协方差是指一种方法可返回从委托的返回类型派生的类型。 禁忌方差意味着一个方法可以采取的参数是该委托的参数类型的基极。 例如,给定这样定义一个委托:
委托对象myCallBack函数(的FileStream S);
它可以构造结合于方法此委托类型的原型的一个实例
像这样:
串的someMethod(流S);
在此,的someMethod的返回类型(字符串)是从委托的返回类型(对象)派生的类型; 该协方差是允许的。 的someMethod的参数类型(流)是一类是一个基类委托的参数类型(的FileStream)的; 此双重方差是允许的。
需要注意的是协方差和反方差仅作参考类型支持,而不是值类型或无效。 因此,举例来说,我不能绑定以下方法给myCallBack函数委托:
INT32 SomeOtherMethod(流S);
尽管SomeOtherMethod的返回类型(Int32)已被从myCallBack函数的返回类型(对象)得到的,这种形式的协方差是不允许的,因为的Int32是值类型。
显然,为什么值类型和空隙不能被用于协方差和反方差的原因是因为这些东西的存储器结构而变化,而对于引用类型的存储器结构始终是一个指针。 幸运的是,如果你试图这样做,不支持一些C#编译器会产生一个错误。