C#学习——6.泛型接口中的协变和抗变

1.变体的由来

众所周知,多态性允许把派生类的对象放在基类的变量中,例如:

Cow myCow = new Cow("牦牛");
Animal myAnimal = myCow;

之所以Cow的类型放在Aniaml类型的变量中可行,是因为Cow派生自Animal。但是,在接口中,这种情况是不适用的,会报错。例如下面的代码:

interface IGetObject<T>
{
   T Dosomething(T value);
}

public class MyClass : IGetObject<string>
{
    public string Dosomething(string value)
    {
       return value;
    }
}

static void Main(string[] args)
{
    IGetObject<string> myClassStr= new MyClass();
    IGetObject<object> myClassObj = myClassStr;
    Console.WriteLine("ok");
    Console.ReadKey();
}

由于MyClass支持IGetObject接口,则主函数第一行代码没有问题。但是,第二行代码预先假定两个接口类型之间有某种关系,即IGetObject类型能够隐式转换为IGetObject类型,实际上这种关系不存在,所以无法进行相应的隐式类型转换,程序会报错,因为我们知道泛型类型的所有类型参数都是不变的。那么怎么解决这个问题呢?对,就是通过变体类型。
如果将IGetObject接口类型的参数T定义为协变类型,IGetObject与IGetObject类型之间就可以建立继承关系,则一种类型的变量就可以包含另外一种类型的值,这与多态性有点类似,但比之更为复杂。
抗变与协变是类似的,但方向相反。抗变不能像协变那样,把泛型接口值放在使用基类型的变量中,但可以把接口放在使用派生类的接口变量中。

2.协变

要把泛型参数定义为协变,需要在类型定义中使用out关键字。对于接口定义,协变类型参数只能用作方法的返回值或属性get访问器。如下例子:

interface IGetObject<out T>
{
   T Dosomething(object value);
}

public class MyClass : IGetObject<string>
{
    public string Dosomething(object value)
    {
       return value.ToString();
    }
}

在主函数中调用:

IGetObject<string> myClassStr = new MyClass();
IGetObject<object> myClassObj = myClassStr;
string msg = "武汉加油!";
string str = (myClassObj.Dosomething(msg)).ToString();
Console.WriteLine(str);

结果如下:

武汉加油!

可以看出:编译器将string Dosomething(object value) 转换为 object Dosomething(object value),即实现了IGetObject到IGetObject 的类型转换,子——>父的转换。

3.抗变

要把泛型参数定义为抗变,需要在类型定义中使用in关键字。对于接口定义,抗变类型参数只能用作方法参数,不能用作返回类型。如下例子:

//作输入参数
interface IMyinterface<in T>
{
   void Dosomething(object value);
}
public class MyClass : IMyinterface<object >
{
   public void  Dosomething(object value)
   {
      Console.WriteLine(value.GetType().ToString());
   }
}

在主函数中调用:

IMyinterface<object> myClass1Obj = new MyClass1();
IMyinterface<string> myClass2Str = myClass1Obj;
List<string> list = new List<string>();
myClass2Str.Dosomething(list);

结果如下:

System.Collections.Generic.List`1[System.String]

可以看出:编译器将void Dosomething(object value) 转换为 void Dosomething(string value) ,即实现了IMyinterface到IMyinterface的类型转换,父——>子的转换。

3.Net Framework中的协变与抗变

占坑,有时间再整理。

4.参考资料

  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 《c#入门经典(第6版)》

欢迎关注我的公众号。
在这里插入图片描述

你可能感兴趣的:(C#,c#)