协变、逆变
个人理解
一.协变和逆变要解决的问题:
(1)让固定泛型接口更加的灵活
(2)让固定泛型接口更加的安全
二.怎么样才能让固定泛型接口更加的安全?
(1)保证类型转换是 子 ==》 父
三.为什么父类型转子类型会带来不安全?
(1)类型不匹配问题
object o = new SubOne();
SubTwo subTwo = o;
以上情况就肯定会产生类型不匹配问题,除非你自己可以百分之百肯定对应类型(这谁能保证嘛)
(2)数据丢失风险
有子类 subOne和subTwo均继承Test.cs,subOne和subTwo拥有的独有成员变量均不同
object o = new SubOne();
SubTwo subTwo = o;
这样子转换之后,会导致数据丢失或不一致的问题
public class Parent { } public class Sub:Parent { } public interface ITest<T> { T OutTest(); void InputTest(T t); }
示例一: ITest<Sub> subTestImpl = new TestImpl<Sub>(); ITest<Parent> parentTest = subTestImpl; 此时parenTest指向的就是 subTestImpl (1)在调用 parentTest.OutTest() 方法的时候,输出的时候会进行 Sub 转成 Parent 的隐式转换(安全) (2)在调用 parenttTest.Input(new Parent()) 方法的时候,传入的参数是父类, 但是实际InputTest方法调用的是subTest的InputTest,则会存在 Parent 转成 Sub 的隐式转换(不安全) 示例二: ITest<Parent> parentTestImpl = new TestImpl<Parent>(); ITest<Sub> subTest = parentTestImpl; 此时subTest指向的是 parentTestImpl (1)在调用 subTest.OutTest() 方法的时候,输出的时候会进行 Parent 转成 Sub 的 隐式转换(不安全) (2)在调用 subTest.InputTest(new Sub()) 传入参数为子类型,而方法的的真正调用是 在 new TestImpl<Parent>()中 也就是会进行 传入参数的隐式转换 ,Sub 转成 Parent 的隐式转换 (安全) 而对于协变和逆变的定义,协变则是示例一,逆变则是示例二。
为什么协变适用于读取而逆变适用与写入?
为什么协变适用于读取而逆变适用与写入? 首先常用的读取和写入方法是如何定义的? 读取:T GetValueByIndex(int index); 写入:void Insert(T t); 由于上可得,协变得时候读取是安全的,逆变的时候写入是安全的。
小结
从以上例子可得,对于接口或委托(lamdb)来说,要保证他们的安全,无非就是两个地方,一个是入参, 一个是出参。 换句话来说,就是得保证入参和出参的隐式转换必须为 子类型转父类型,而不出现 父类型转子类型的情况! 而最根本的原因还是你的这个对外的类型和真正使用的类型之间的转换 是否是安全的(是否是子类型转父类型 或是给予的是原本类型) 比如:ITest<Sub> subTest = parentTestImpl; 他对外的类型是 Sub , 但是他真正指向实际的调用是 new TestImpl<Parent>(),那就会存在隐式转 换安全问题 ITest<Parent> parentTest = subTestImpl;同理得。
根据上述,对于一个泛型参数,他是不会同时支持协变和逆变的, 而协变定义的就是这种情况: ITest<Sub> subTestImpl = new TestImpl<Sub>(); ITest<Parent> parentTest = subTestImpl; 泛型参数作为返回参数是安全的,out关键字 仅能用作返回参数。 而逆变定义的就是这种情况: ITest<Parent> parentTestImpl = new TestImpl<Parent>(); ITest<Sub> subTest = parentTestImpl; 泛型参数作为传入参数是安全的,in关键字 仅能用作传入参数。 只有在泛型接口或泛型委托中才能使用协变和逆变关键字
总结
最根本的原因还是,变量的对外类型 和 变量真正指向的类型 之间的隐式转换是否是安全的。
如:
ITest<Sub> subTestImpl = new TestImpl<Sub>();
ITest<Parent> parentTest = subTestImpl;
parentTest.OutTest();
parentTest的对外类型是 Parent,但是他实际真正调用进入的OutTest方法是new TestImpl<Sub>()中的
OutTest方法,而这个方法的类型是Sub,而不是Parent