在.net中有一种方式=》里氏转换,这是针对对象转换的一种方式,子类对象可以赋值给父类,相反,如果父类中包含子类对象,可以强转为子类,微软还推出了两个关键字as与is。

  1. //父类 = 子类
  2. string str = "string";
  3. object obj = str;//变了

而C#里又有泛型的概念,泛型是对类型系统的进一步抽象,比上面简单的类型高级,把上面的变化体现在泛型的参数上就是我们所说的逆变与协变的概念。通过在泛型参数上使用in或out关键字,可以得到逆变或协变的能力,微软也推出了相应的关键字out协变与in逆变。下面是一些对比的例子:

协变(Foo<父类> = Foo<子类> ):

  1. //泛型委托:
  2. public delegate T MyFuncA<T>();//不支持逆变与协变
  3. public delegate T MyFuncB<out T>();//支持协变
  4. MyFuncA<object> funcAObject = null;
  5. MyFuncA<string> funcAString = null;
  6. MyFuncB<object> funcBObject = null;
  7. MyFuncB<string> funcBString = null;
  8. MyFuncB<int> funcBInt = null;
  9. funcAObject = funcAString;//编译失败,MyFuncA不支持逆变与协变
  10. funcBObject = funcBString;//变了,协变
  11. funcBObject = funcBInt;//编译失败,值类型不参与协变或逆变
  12. //泛型接口
  13. public interface IFlyA<T> { }//不支持逆变与协变
  14. public interface IFlyB<out T> { }//支持协变
  15. IFlyA<object> flyAObject = null;
  16. IFlyA<string> flyAString = null;
  17. IFlyB<object> flyBObject = null;
  18. IFlyB<string> flyBString = null;
  19. IFlyB<int> flyBInt = null;
  20. flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变
  21. flyBObject = flyBString;//变了,协变
  22. flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变
  23. //数组:
  24. string[] strings = new string[] { "string" };
  25. object[] objects = strings;

逆变(Foo<子类> = Foo<父类>):

  1. public delegate void MyActionA<T>(T param);//不支持逆变与协变
  2. public delegate void MyActionB<in T>(T param);//支持逆变
  3. public interface IPlayA<T> { }//不支持逆变与协变
  4. public interface IPlayB<in T> { }//支持逆变
  5. MyActionA<object> actionAObject = null;
  6. MyActionA<string> actionAString = null;
  7. MyActionB<object> actionBObject = null;
  8. MyActionB<string> actionBString = null;
  9. actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败
  10. actionBString = actionBObject;//变了,逆变
  11. IPlayA<object> playAObject = null;
  12. IPlayA<string> playAString = null;
  13. IPlayB<object> playBObject = null;
  14. IPlayB<string> playBString = null;
  15. playAString = playAObject;//IPlayA不支持逆变与协变,编译失败
  16. playBString = playBObject;//变了,逆变

根据上面的例子。可以得出以下几点

  1. //1.以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
  2. //2.当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
  3. //3.值类型不参与逆变与协变。

注意问题

那么in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?

原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键作为泛型修饰符的话,那么那个泛型参数就只能用作方法的返回值。

当尝试编译下面这个把in泛型参数用作方法返回值的泛型接口时:

  1. `public` `interface` `IPlayB<``in` `T>``{`` ``T Test();``}`

出现了如下编译错误:

错误 1 方差无效: 类型参数“T”必须为“CovarianceAndContravariance.IPlayB.Test()”上有效的 协变式。“T”为 逆变。

到这里,我们大致知道了逆变与协变的相关概念。

底层实现

我想他的底层应该也是里式转换的规则:协变不是理所当然的,逆变也没有“逆”。