C#4.0新特性之协变与逆变实例分析

时间:2022-02-09 08:49:32


一、C#3.0以前的协变与逆变

如果你是第一次风闻这个两个词,别担忧,他们其实很常见。C#4.0中的协变与逆变(Covariance and contravariance)有了进一步的完善,主要是两种运行时的(隐式)泛型类型参数转换。简单来讲,所谓协变(Covariance)是指把类型从“小”升到“大”,好比从子类升级到父类;逆变则是指从“大”变到“小”,两者各有差此外条件和用途。下面的例子演示了C#3.0以前对协变与逆变撑持 :

代码1

public class Animal { } public class Cat : Animal { } public delegate Animal AniHandler(Animal a); public static Animal AniMethod(Animal a) { return null; } public static Cat CatMethod(Object o) { return null; } public static void TestCovariance() { AniHandler handler1 = AniMethod; AniHandler handler2 = CatMethod;//这里是合法的 }

这里的CatMethod虽然不是严格满足委托AniHandler的签名,但它被用作AniHandler是合法的,在协变(Cat->Animal)和逆变(object->Animal)的感化下,委托指向的要领中,传入的参数可以是一个大的,宽泛的类型,而返回出来的功效可以是一个更小的,精确的类型(子类),因为它包罗了更多的信息。注意这里是站在要领里面这样说的,而在挪用者使用要领的角度,恰恰是相反的,在挪用要领时,参数可以是一个“小”的子类,而返回值可以用作一个“大”的父类,如下面的挪用是合法的:

object o = AniMethod(new Cat());

呵呵,听上去有点晕,此刻我要试着把问题简洁地表达清楚。无论是协变还是逆变,它都是为了让这样一个非常合理的事实创立:如果供给的类型信息比所需要的类型信息多(而不是相等),那这固然是可以的。在代码1的例子中,AniHandler委托需要一个Animal作为返回值,但是我返给它一个Cat,Cat包罗了Animal的所有特征,这固然是可以的,这就是协变;同时AniHandler需要一个Animal作为参数,为了让函数获得的信息比要求的多,我可以只要求传进来一个object,这也固然是可以的,这就是逆变。


二、C#4.0中的协变

我们先来看一下和谐的协变是如何产生的。C#4.0中的协变与C#3.0中的宽松委托非常类似,新的C#协变特征还表此刻泛型接口或者泛型委托的类型参数上。还是以经典的Animal和Cat为例,在你看过上面代码1之后,既然Cat CatMethod()可以被用作Animal AniHandler,那么你完全有理由相信下面的代码在C#3.0中也是合法的:

代码3

delegate T THandler<T>(); static void Main(string[] args) { THandler<Cat> catHandler= () => new Cat(); THandler<Animal> aniHandler = catHandler;//Covariance }

很遗憾,您错了,,在C#3.0中,上面的代码不能通过编译,你会被奉告产生错误!

时代进步了,此刻在C#4.0的编译器是撑持上面的写法的。你只需要在声明THandler的类型参数前加一个out关键字即可:

delegate T THandler<out T>();

单独的使用一个关键字而不是直接允许隐式转换也是为了类型安适的考虑。所以当你写下out的时候,就应该知道可能产生的Covariance。


三、C#4中的逆变

我们继续使用Animal和Cat的例子,在VS2008中,以下的代码不能通过编译:

代码5

delegate void THandler<T>(T t); public static void TestContravariance() { THandler<Animal> aniHandler = (ani) => { }; THandler<Cat> catHandler = aniHandler; }

而在VS2010中,呃,同样不能。呵呵,其实就差一点点,这里如果在类型参数T前面加上关键字“in”,即delegate void THandler<in T>(T t);就可以实现Cat->Animal的Contravariance。


四、总结:

C#4中的协变和逆变使得泛型编程时的类型转换越发自然,不过要注意的是上面所说的协变和逆变都只感化于引用类型之间,而且目前只能对泛型接口和委托使用。一个T参数只能是in或者是out,你如果即想你的委托参数逆变又想返回值协变(如代码1所示),是做不到的。