C# 4.0 新特性之"协变"与"逆变"[转]
2011-01-18 16:24
405 查看
协变和逆变,这两个词的翻译实在很难表达出他们的真实含义。其实他们是继承和多态的衍生物,而且在.Net1.0和2.0中都提供了某种程度的支持,只是在.Net4.0中支持的更加完善了。
简单说来,协变和逆变就是希望支持更多情况的隐式类型转换,使得我们的编程更加方便,而通常来说只有具备继承关系的两个对象才可以发生隐式类型转换,如
Baseb=newDerived().协变和逆变则使得更多的类型之间可以发生隐式类型转换,如通过协变以下代码可以正常工作:
那么我们为什么需要这种功能呢?让我们从一个多态的例子开始:
虽然FeedAnimal方法接受的参数类型为Animal,但是当我们传入一个Tiger的实例,方法能够编译通过,并得到正确的执行。这是一个典型的多态运用。之所以能够这么做是因为Tiger继承于Animal,在FeedAnimal方法中对Animal对象的各种操作,Tiger对象同样支持,而不会发生调用了animal的某个方法A,而tiger中不存在A方法的情况。那么如果我们把这一原则推而广之,就出现了协变这一概念。
现在我们让FeedAnimal方法接受一个能够返回Animla的delegate,而同时又创建了一个能够返回Tiger的方法。按照之前的推
理,FeedAnimal方法无非就是对delegate中返回的Animal对象进行各种操作,那么如果给它一个返回Tiger对象的
delegate,也应该能够正常的工作。所以下面这段代码是可以在.NET2.0中工作的。
这就是所谓的协变,我们希望两个对象之间,除了继承关系外也能做隐式类型的转换,因为我们认为这种转换是合理的,是类型安全的。
大家可能很奇怪,如果.Net2.0就已经支持了以上的代码,那么4.0又搞了些什么?
先来看看下面的代码:
以上这段代码的输出结果是Feed
Mammal,当碰到两个匹配的重载方法时,.Net编译器选择了形参类型在继承树上更接近自己的方法。可是如果我们把Animal改成
Func<Animal>以下代码就编译出错了,编辑器不知道选择哪个方法作为匹配。
究其根本,在C#2.0中你无法实现以下delegate间的隐式类型转换
而C#4.0,使得上述代码能够成功运行,之前的两个方法他也能够做出正确的选择。这是通过out关键字实现的。注意Func<TResult>这个delegate在C#2.0和4.0中的定义是不同的:
out关键字表示该类型用于返回值,而返回值可以发生协变,因为一个方法如果能够接受一个返回Animal的delegate,那么他必然也可以接受一个返回Tiger的delegate,因为他对animla的操作同样可以作用于tiger上。
既然delegate能够支持协变,那么interface也应该给予支持,因为它无非就是多个delegate罢了。
以下代码在C#2.0中式不能通过编译的,这样看来interface还不如delegate支持的好。
下面的代码也就更加不行了:
到了C#4.0中,由于out关键字的缘故,以上两段代码都能正常工作了。而IEnumerable<T>的定义也被改为
之前举得例子都是描述协变的,其实逆变无非就是扩大了输入参数的类型转换的可能,不过方向是反的。
在Execute方法中,我们接受一个类型为Action<Mammal>
的delegate,我们在使用这个delegate时,可以传入各种类型的Mammal,比如Tiger,
Lion。如果我们传入的delegate能够作用于Animal对象,那么他自然可以作用于Tiger,Lion。
所以我们可以传入一个输入类型更抽象的delegate。因此,在逆变的支持下我们可以写出以下代码。
参考资料:eric'sseriesoncovarianceandcontravariance
来源:/article/4606617.html
简单说来,协变和逆变就是希望支持更多情况的隐式类型转换,使得我们的编程更加方便,而通常来说只有具备继承关系的两个对象才可以发生隐式类型转换,如
Baseb=newDerived().协变和逆变则使得更多的类型之间可以发生隐式类型转换,如通过协变以下代码可以正常工作:
Func<Derived>dFunc=GetDFunc(); Func<Base>bFunc=dFunc; IEnumerable<Derived>dEnum=GetDEnum(); IEnumerable<Base>bEnum=dEnum;
那么我们为什么需要这种功能呢?让我们从一个多态的例子开始:
abstractclassAnimal { internalabstractvoidEat(); } abstractclassMammal:Animal { } classTiger:Mammal { internaloverridevoidEat() { Console.WriteLine("Tigereat"); } }
classProgram { staticvoidMain(string[]args) { Tigertiger=newTiger(); FeedAnimal(tiger); } staticvoidFeedAnimal(Animalanimal) { animal.Eat(); } }
虽然FeedAnimal方法接受的参数类型为Animal,但是当我们传入一个Tiger的实例,方法能够编译通过,并得到正确的执行。这是一个典型的多态运用。之所以能够这么做是因为Tiger继承于Animal,在FeedAnimal方法中对Animal对象的各种操作,Tiger对象同样支持,而不会发生调用了animal的某个方法A,而tiger中不存在A方法的情况。那么如果我们把这一原则推而广之,就出现了协变这一概念。
staticvoidFeedAnimal(Func<Animal>animalCreator) { varanimal=animalCreator(); animal.Eat(); } staticTigerCreateTiger() { returnnewTiger(); }
现在我们让FeedAnimal方法接受一个能够返回Animla的delegate,而同时又创建了一个能够返回Tiger的方法。按照之前的推
理,FeedAnimal方法无非就是对delegate中返回的Animal对象进行各种操作,那么如果给它一个返回Tiger对象的
delegate,也应该能够正常的工作。所以下面这段代码是可以在.NET2.0中工作的。
classProgram { staticvoidMain(string[]args) { FeedAnimal(CreateTiger); } }
这就是所谓的协变,我们希望两个对象之间,除了继承关系外也能做隐式类型的转换,因为我们认为这种转换是合理的,是类型安全的。
大家可能很奇怪,如果.Net2.0就已经支持了以上的代码,那么4.0又搞了些什么?
先来看看下面的代码:
classProgram
{
staticvoidMain(string[]args)
{
Tigertiger=newTiger();
FeedAnimal(tiger);
}
staticvoidFeedAnimal(Animalanimal)
{
Console.WriteLine("FeedAnimal");
}
staticvoidFeedAnimal(Mammalmammal)
{
Console.WriteLine("FeedMamal");
}
}
以上这段代码的输出结果是Feed
Mammal,当碰到两个匹配的重载方法时,.Net编译器选择了形参类型在继承树上更接近自己的方法。可是如果我们把Animal改成
Func<Animal>以下代码就编译出错了,编辑器不知道选择哪个方法作为匹配。
classProgram
{
staticvoidMain(string[]args)
{
FeedAnimal(CreateTiger);
}
staticTigerCreateTiger(){returnnewTiger();}
staticvoidFeedAnimal(Func<Animal>animalCreator)
{
varanimal=animalCreator();
animal.Eat();
}
staticvoidFeedAnimal(Func<Mammal>animalCreator)
{
varanimal=animalCreator();
animal.Eat();
}
究其根本,在C#2.0中你无法实现以下delegate间的隐式类型转换
Func<Tiger>tFunc=CreateTiger;
Func<Animal>aFunc=tFunc;
而C#4.0,使得上述代码能够成功运行,之前的两个方法他也能够做出正确的选择。这是通过out关键字实现的。注意Func<TResult>这个delegate在C#2.0和4.0中的定义是不同的:
//C#2.0
publicdelegateTResultFunc<TResult>();
//C#4.0
publicdelegateTResultFunc<outTResult>();
out关键字表示该类型用于返回值,而返回值可以发生协变,因为一个方法如果能够接受一个返回Animal的delegate,那么他必然也可以接受一个返回Tiger的delegate,因为他对animla的操作同样可以作用于tiger上。
既然delegate能够支持协变,那么interface也应该给予支持,因为它无非就是多个delegate罢了。
以下代码在C#2.0中式不能通过编译的,这样看来interface还不如delegate支持的好。
staticvoidMain(string[]args)
{
List<Tiger>tigers=newList<Tiger>();
FeedAnimals(tigers);
}
staticvoidFeedAnimals(IEnumerable<Animal>animals)
{
foreach(varanimalinanimals)
animal.Eat();
}
下面的代码也就更加不行了:
List<Tiger>tigers=newList<Tiger>();
IEnumerable<Animal>animlas=tigers;
到了C#4.0中,由于out关键字的缘故,以上两段代码都能正常工作了。而IEnumerable<T>的定义也被改为
publicinterfaceIEnumerable<outT>:IEnumerable
{
IEnumerator<T>GetEnumerator();
}
之前举得例子都是描述协变的,其实逆变无非就是扩大了输入参数的类型转换的可能,不过方向是反的。
staticvoidFeedAnimal(Animalanimal)
{
animal.Eat();
}
staticvoidExecute(Action<Mammal>tAct)
{
tAct(newTiger());
}
在Execute方法中,我们接受一个类型为Action<Mammal>
的delegate,我们在使用这个delegate时,可以传入各种类型的Mammal,比如Tiger,
Lion。如果我们传入的delegate能够作用于Animal对象,那么他自然可以作用于Tiger,Lion。
所以我们可以传入一个输入类型更抽象的delegate。因此,在逆变的支持下我们可以写出以下代码。
staticvoidMain(string[]args)
{
Action<Animal>aAct=FeedAnimal;
Action<Mammal>mAct=aAct;
Execute(aAct);
}
参考资料:
来源:
相关文章推荐
- 好文章——C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# 4.0新特性——“协变”与“逆变”以及背后的编程思想(转)
- C# 新特性_协变与逆变 (.net 4.0)
- C#4.0新特性之(三)协变与逆变
- C# 4.0新特性——“协变”与“逆变”以及背后的编程思想
- 精进不休 .NET 4.0 (4) - C# 4.0 新特性之命名参数和可选参数, 动态绑定(dynamic), 泛型协变和逆变, CountdownEvent, Barrier
- 精进不休 .NET 4.0 (4) - C# 4.0 新特性之命名参数和可选参数, 动态绑定(dynamic), 泛型协变和逆变, CountdownEvent, Barrier
- [转]C#4.0中的协变和逆变
- 精进不休 .NET 4.0 (4) - C# 4.0 新特性之命名参数和可选参数, 动态绑定(dynamic), 泛型协变和逆变, CountdownEvent, Barrier
- C# 4.0中的协变和逆变(一)
- 一起谈.NET技术,C#4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# 4.0中的协变和逆变
- <转>C# 4.0 为泛型编程引入了 协变 和 逆变 支持,这是个不错的福利,能省掉以往的一些麻烦。不过当前(Beta2)仅支持泛型接口和泛型委托。
- <转>C# 4.0 为泛型编程引入了 协变 和 逆变 支持,这是个不错的福利,能省掉以往的一些麻烦。不过当前(Beta2)仅支持泛型接口和泛型委托。
- C#4.0 新特性, dynamic, 可选参数,协变与抗变 (转)
- C#4.0新特性-"协变"“.NET研究”与"逆变"以及背后的编程思想