-
协变和逆变
-
协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。 协变保留分配兼容性,逆变则与之相反。
-
协变
- 以下代码演示支持协变与不支持协变的泛型和数组的区别
-
//泛型委托 public delegate T MyFuncA<T>();//不支持逆变与协变 public delegate T MyFuncB<out T>();//支持协变 //泛型接口 public interface IFlyA<T> { }//不支持逆变与协变 public interface IFlyB<out T> { }//支持协变 private static void Main(string[] args) { string str = "string"; object obj = str; Console.WriteLine("c#中可以安全地把str的引用赋给obj引用"); MyFuncA<object> funcAObject = null; MyFuncA<string> funcAString = null; MyFuncB<object> funcBObject = null; MyFuncB<string> funcBString = null; MyFuncB<int> funcBInt = null; //funcAObject=funcAString;//编译失败,MyFuncA不支持逆变与协变 funcBObject = funcBString;//编译成功,MyFuncB支持协变 //funcBObject=funcBInt;//编译失败,值类型不参与协变 Console.WriteLine("支持协变的泛型,可以安全地把funcBObject的引用赋给funcBObject引用"); //接口 IFlyA<object> flyAObject = null; IFlyA<string> flyAString = null; IFlyB<object> flyBObject = null; IFlyB<string> flyBString = null; IFlyB<int> flyBInt = null; //flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变 flyBObject = flyBString;//编译成功,IFlyB支持协变 //flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变 Console.WriteLine("支持协变的泛型接口同理,可以安全地把flyBString的引用赋给flyBObject引用"); //数组: string[] strings = new string[] { "string" }; object[] objects = strings; Console.WriteLine("数组可以安全地把strings的引用赋给objects引用"); Console.ReadLine(); }
关于协变的定义:
- 协变就是对具体成员的输出参数进行一次类型转换,且类型转换的准则是 “里氏替换原则”。
-
逆变
-
//泛型委托 public delegate T MyFuncA<T>();//不支持逆变与协变 public delegate T MyFuncB<out T>();//支持协变 public delegate void MyActionA<T>(T param);//不支持逆变与协变 public delegate void MyActionB<in T>(T param);//支持逆变 //泛型接口 public interface IFlyA<T> { }//不支持逆变与协变 public interface IFlyB<out T> { }//支持协变 public interface IPlayA<T> { }//不支持逆变与协变 public interface IPlayB<in T> { }//支持逆变 private static void Main(string[] args) { string str = "string"; object obj = str; Console.WriteLine("c#中可以安全地把str的引用赋给obj引用"); MyFuncA<object> funcAObject = null; MyFuncA<string> funcAString = null; MyFuncB<object> funcBObject = null; MyFuncB<string> funcBString = null; MyFuncB<int> funcBInt = null; //funcAObject=funcAString;//编译失败,MyFuncA不支持逆变与协变 funcBObject = funcBString;//编译成功,MyFuncB支持协变 //funcBObject=funcBInt;//编译失败,值类型不参与协变 Console.WriteLine("支持协变的泛型,可以安全地把funcBObject的引用赋给funcBObject引用"); //接口 IFlyA<object> flyAObject = null; IFlyA<string> flyAString = null; IFlyB<object> flyBObject = null; IFlyB<string> flyBString = null; IFlyB<int> flyBInt = null; //flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变 flyBObject = flyBString;//编译成功,IFlyB支持协变 //flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变 Console.WriteLine("支持协变的泛型接口同理,可以安全地把flyBString的引用赋给flyBObject引用"); //数组: string[] strings = new string[] { "string" }; object[] objects = strings; Console.WriteLine("数组可以安全地把strings的引用赋给objects引用"); Console.ReadLine(); object[] array = new String[10]; // MyActionA<object> actionAObject = null; MyActionA<string> actionAString = null; MyActionB<object> actionBObject = null; MyActionB<string> actionBString = null; actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败 actionBString = actionBObject;//变了,逆变 IPlayA<object> playAObject = null; IPlayA<string> playAString = null; IPlayB<object> playBObject = null; IPlayB<string> playBString = null; playAString = playAObject;//IPlayA不支持逆变与协变,编译失败 playBString = playBObject;//变了,逆变 }
关于逆变的定义
- 逆变就是对具体成员的输入参数进行一次类型转换,且类型转换的准则是"里氏替换原则"。
-
总结
-
总结有以下几点:
- 以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
- 当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
- 值类型不参与逆变与协变。
那么in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?
原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键字反之。
-
-
自问自答
-
1、协变、逆变 为什么只能针对泛型接口或者委托?而不能针对泛型类?
因为它们都只能定义方法成员(接口不能定义字段),而方法成员在创建对象的时候是不涉及到对象内存分配的,所以它们是类型(内存)安全的。
为什么不针对泛型?因为泛型类是模板类,而类成员是包含字段的,不同类型的字段是影响对象内存分配的,没有派生关系的类型它们是不兼容的,也是内存不安全的。
2、协变、逆变 为什么是类型安全的?
本质上是里氏替换原则,由里氏替换原则可知:派生程度小的是派生程度大的子集,所以子类替换父类的位置整个程序功能都不会发生改变。
3、官方对 协变、逆变 的定义现在是否能看懂?
上面看懂了,官方定义肯定也是没问题的。派生程度小可以理解为基类,派生程度大可以理解为子类或派生类,至于为什么用程度这个词,是因为继承链的深度是没限制的。
4、为什么 in 、out 只能是单向的(输入或输出)?
因为若类型参数同时为输入参数和输出参数,则必然会有一种转换不符合里氏替换原则,即将父类型的变量赋值给子类型的变量,这是不安全的所以需要明确指定 in 或 out。
-
参考文章链接
-
https://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html
- https://www.cnblogs.com/JingShaoHui/p/14321835.html
- https://zhuanlan.zhihu.com/p/629649432
- https://blog.csdn.net/poorkick/article/details/114006840