[关闭]
@wddpct 2017-04-03T13:22:21.000000Z 字数 1435 阅读 737

c# 协变和逆变的理解

c#


1. 是什么

1.1 协变

协变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。如 string 到 object 的转换。

1.2 逆变

逆变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型。如 object 到 string 的转换。

2. 怎么理解

假如有一个 sub 子类和 parent 父类,我们可以很轻易地将 sub 转换成 parent,这是类型安全的,反之则不行。其实很好理解,子类必然具有相同或更多的属性和方法,所以转换成属性和方法可能更好的父类,只需要去除自身的部分属性和方法即可,这对编译器而言是可行的。反之,你怎么要求编译器为父类增加更多的成员呢。数组也继承了这一特性,对于一个string[]类型而言

理解了上述概念后,让我们来看看协变和逆变的概念,这里我们只谈谈关于接口可变性中的一些内容。以下我简单给出一个接口及其实现。

  1. public interface IMyInTerface<T>
  2. {
  3. T Method1();
  4. void Method2(T t);
  5. }
  6. public class MyClass<T> : IMyInTerface<T>
  7. {
  8. public T Method1()
  9. {
  10. return default(T);
  11. }
  12. public void Method2(T t)
  13. {
  14. Console.WriteLine(typeof(T).Name);
  15. }
  16. }

对应的调用方法代码如下

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. // 协变演示
  6. IMyInTerface<Sub> sub = new MyClass<Sub>();
  7. Parent parent1 = sub.Method1();
  8. // 逆变演示
  9. IMyInTerface<Parent> parent2 = new MyClass<Parent>();
  10. parent2.Method2(new Sub());
  11. //// 协变演示
  12. //IMyInTerface<Parent> parent = new MyClass<Parent>();
  13. //Sub sub1 = parent.Method1();
  14. //// 逆变演示
  15. //IMyInTerface<Sub> sub2 = new MyClass<Sub>();
  16. //sub2.Method2(new Parent());
  17. Console.ReadKey();
  18. }
  19. }

以上调用代码都是正常的,但是当我们将上述代码的子类和父类的位置调换,换成上述注释中的代码,编译器则会报错。这便是违背了本文最重要的一个原则:类型转换中,对编译器而言只有子类到父类的转化才是安全的

比如说,我们不能把 parent 的实例 赋值给 sub1 ,我们也不能把 new Parent() 代替 Sub 传入 Method2() 方法。

所以这一切,都是命运石之门的选择呀。

也正是因此,为了防止开发者写出错误的代码,.net 设计者便用了协变和逆变(对应 out 和 in 关键字)来强制要求正确行为。也就是说,即使你想这么做,一旦你用了微软提供的 IEnumerable 等接口,你也无法进行错误的类型转换了。所以归根到底,协变和逆变只是一种约束而已,这种规范限制了你的泛型接口中要么只能有将类型参数当作返回值的协变相容方法(加了 out 关键字),要么只能有将类型参数当作输入值的逆变相容方法(加了 in 关键字)。

本文针对的是对协变和逆变存在部分理解但是仍然有些迷糊的开发者群体,所以只是撷取了重要概念解惑答疑,希望能帮到一部分人,这就是我的期望了。假如有不理解的,欢迎在评论区贴出即可。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注