协变(covariant)和逆变(contravariant)
2014-01-21 16:15 xiashengwang 阅读(738) 评论(1) 编辑 收藏 举报我们知道子类转换到父类,在C#中是能够隐式转换的。这种子类到父类的转换就是协变。
而另外一种类似于父类转向子类的变换,可以简单的理解为“逆变”。
上面对逆变的简单理解有些牵强,因为协变和逆变只能针对接口和代理类型。而父类和子类之间不存在这种逆变的概念。
协变和逆变的本质都是子类安全的转到父类的过程。
下面就来加深下印象,先定义两个类Car和Baoma
public class Car { } public class Baoma : Car { }
明显Baoma(宝马)是Car的子类
1,先来看看协变
协变在C#中要用out关键字标明,用这个关键字就表示参数T只能用于函数,属性等的返回值。
//协变(covariant) public interface ICo<out T> { T Test1(); }
//协变(covariant) ICo<Car> ico1 = null; ICo<Baoma> ico2 = null; ico1 = ico2;//子类-->父类 Car car = ico1.Test1();//实际调用ico2.Test1()返回Baoma类型,当然可以赋值给父类Car //ico2 = ico1;//error //Baoma baoma = ico2.Test1();//实际调用ico1.Test1()返回Car类型,赋值给子类Baoma,显然不能保证类型安全
2,在来看看逆变
逆变在C#中用in关键字标明,表明参数T只能用于函数的参数。
//逆变(contravariant) public interface IContra<in T> { void Test1(T t); }
//逆变(contravariant) IContra<Car> icontra1 = null; IContra<Baoma> icontral2 = null; //icontra1 = icontral2;//error //icontra1.Test(new Car()); 实际调用IContra<Baoma>.Test(Baoma),参数Car不能安全的转换为Baoma icontral2 = icontra1;//看似很奇怪,但这里不是Car->Baoma,而是IContra<Car>->IContra<Baoma> icontral2.Test1(new Baoma());//实际调用IContra<Car>.Test(Car),参数Baoma能安全的转换为Car
上面的代码中已经标注详细了调用的过程,对于理解很有帮助。下面分析两种复杂一点的过程。
3,带有逆变或协变的接口作为函数参数
public interface IFoo<in T> { void Test(T t); } public interface IBar<out T>//这里T必须用out,而不是in { void Test(IFoo<T> foo); }
IFoo<Car> ifoo1 = null; IBar<Car> ibar1 = null; IBar<Baoma> ibar2 = null; ibar1 = ibar2;//协变 ibar1.Test(ifoo1);//实际调用IBar<Baoma>的Test(IFoo<Baoma>), //要求IFoo<Car>要转换到IFoo<Baoma>,这就需要IFoo对T逆变,即用in关键字
引用装配脑袋对这种情况的归纳:
//如果一个接口需要对T协变,那么这个接口所有方法的参数类型必须支持对T的反变。
//同理我们也可以看出,如果接口要支持对T反变,那么接口中方法的参数类型都必须支持对T协变才行。
//这就是方法参数的协变-反变互换原则。
4,带有逆变或协变的接口作为函数的返回值
public interface IFoo<in T> { void Test(T t); } public interface IBar2<in T> { IFoo<T> Test(); }
IBar2<Car> a = null; IBar2<Baoma> b = null; b = a;//逆变 IFoo<Baoma> ibaoma = b.Test();//实际调用IBar2<Car>.Test返回IFoo<Car>类型,IFoo<Car>要转换成IFoo<Baoma> //需要IFoo对T逆变,这种函数返回值的转换方向是一致的。
引用装配脑袋对这种情况的归纳:
//如果一个接口需要对T进行协变或反变,那么这个接口所有方法的返回值类型必须支持对T同样方向的协变或反变。
//这就是方法返回值的协变-反变一致原则。
以上仅仅是个人理解归纳,关于这个概念的理解,可以参看园子里的这两篇文章,写的非常详细。
装配脑袋的:
http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html
还有这篇,用图的方式很好理解:
http://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html