首页 > 代码库 > c#协变与逆变心得
c#协变与逆变心得
在读本文之前请先阅读此文:
http://www.cnblogs.com/CLR010/p/3274310.html
对于协变与逆变,始终记住三个原则,记住这三点就一切清晰了!
1.不管协变还是逆变,最终遵循的类型变换是===》子类转换为父类,比如string=》object;即被派生类转为派生类;
2.协变修饰为out,协变时,协变修饰的类型参数只能作为返回值!不能用于输入参数,编译不过去;
3.逆变修饰符为in,逆变时,逆变修饰的类型参数只能作为输入参数!不能用于返回值,编译不过去;
/// <summary> /// /// </summary> /// <typeparam name="TN">逆变类型参数</typeparam> /// <typeparam name="TX">协变类型参数</typeparam> interface IDemo<in TN, out TX> { /// <summary> ///协变,out修饰类型参数Y只能用于返回值! /// </summary> /// <returns></returns> TX Xb(); /// <summary> /// 逆变,in 修饰的类型参数T只能用于输入参数! /// </summary> /// <param name="param"></param> void Nb(TN param); /// <summary> /// 组合的例子 /// </summary> /// <param name="param"></param> /// <returns></returns> TX Get(TN param); } class A:IDemo<object,string> { public string Xb() { throw new NotImplementedException(); } public void Nb(object param) { throw new NotImplementedException(); } public string Get(object param) { throw new NotImplementedException(); } } class B:IDemo<string,object> { public object Xb() { throw new NotImplementedException(); } public void Nb(string param) { throw new NotImplementedException(); } public object Get(string param) { throw new NotImplementedException(); } }
static void Main() { //参数类型完全一样,可以编译通过 IDemo<string,object> correctDemo=new B(); //注意correctDemo2的声明方式 IDemo<string, object> correctDemo2= new A(); //正确的方式 object result= correctDemo2.Get("111"); //参数类型错误 //correctDemo2.Get(new object()); //无法协变和逆变 //IDemo<object, string> wrongDemo = new B(); }
分析
1.我们通过IDemo<string, object>声明了correctDemo2,所以correctDemo2的Get方法的签名是object Get(string p);注:根据原则2、3,string为逆变参数,只能做输入参数,object为协变参数,只能做返回值
2.根据类A的定义,我们得出类A的Get方法的签名是string Get(object param)
3.correctDemo2的引用指向了类A的实例!所以correctDemo2执行时,实际是执行的方法其实是酱紫的string Get(object param),就是A的Get方法
4.但是我们在调用correctDemo2的Get方法时传递的参数是string类型的啊!可A的Get方法接受的是object类型的参数啊!OK我们的原则1出场(回上面瞅瞅),string=》object遵循被派生类转为派生类的原则;所以Get方法可以执行了
5.然后继续,A的Get方法执行后返回的结果是string类型!而correctDemo2实际是接受一个object类型的结果;OK我们的原则1再出场,string=》object遵循被派生转为派生类的原则;所以Get方法成功返回结果~
6.第四步就是逆变的过程,第五步就是协变的过程;
这是correctDemo2在调用时显示的信息,需要string类型的参数,返回object的结果;
然后我们调试的时候用即时窗口看看correctDemo2
原来是A类型,只不过调用时是通过接口的方法签名调用的
然后再看看correctDemo2的方法
实际上是object的参数,string的结果;
整个的执行过程是酱紫的;
图片较宽,可以右键,在新窗口中打开
//7.所谓协变可以理解为就是子类转换为基类,逆变理解为基类转换为子类;因为我们是把A赋值给了IDemo,貌似是既有协变又有逆变,实际上只有“协变”;如上图,类型的转化都是从子类转换为父类!因为面向对象是不允许子类声明父类的;
要实现逆变和协变必须要in和out关键字实现;
举例说明,比如
/// <summary> /// /// </summary> /// <typeparam name="T">既是返回值类型又是输入参数类型</typeparam> internal interface Icommon<T> { /// <summary> /// 既是返回值类型又是输入参数类型 /// </summary> /// <param name="t"></param> /// <returns></returns> T Geta(T t); } class C:Icommon<string> { /// <summary> /// 返回值是string,输入参数也是string /// </summary> /// <param name="t"></param> /// <returns></returns> public string Geta(string t) { throw new NotImplementedException(); } } class D : Icommon<object> { /// <summary> /// 返回值是object,输入参数也是object /// </summary> /// <param name="t"></param> /// <returns></returns> public object Geta(object t) { throw new NotImplementedException(); } }
static void Main() { //无法编译 Icommon<object> ic = new C(); //无法编译 Icommon<string> id = new D(); }
以上,ic有协变的条件,无法满足逆变;因为类型参数即使返回值又是输入参数;c的Geta输入参数和返回值都是string,而ic要求返回值和输入参数都是object,返回值string=》object的协变条件是成立的,但是输入参数object=》string是背离原则【子类转换为父类】的
id则有逆变的条件,无法满足协变;
个人的理解,纯属抛砖引玉,不当的地方请指正;
c#协变与逆变心得