重学c#系列——逆变和协变[二十四]

前言

简单整理一下逆变和协变。

正文

什么是逆变和协变呢?

首先逆变和协变都是术语。

协变表示能够使用比原始指定的派生类型的派生程度更大的类型。

逆变表示能够使用比原始指定的派生类型的派生程度更小的类型。

重学c#系列——逆变和协变[二十四]

这里student 继承 person。

这里这个报错合情合理。

这里可能有些刚入门的人认为,person 不是 student 的父类啊,为什么不可以呢?

一个列表student 同样也是一个列表的 person啊。

这可能是初学者的一个疑问。

&#x4F46;&#x662F;&#x5B9E;&#x9645;&#x60C5;&#x51B5;&#x662F;list<person> &#x662F;&#x4E00;&#x4E2A;&#x7C7B;&#x578B;&#xFF0C; list<student> &#x662F;&#x4E00;&#x4E2A;&#x7C7B;&#x578B;&#x3002;
</student></person>

重学c#系列——逆变和协变[二十四]

所以他们无法隐式转换是正常的。

但是这样写就可以:

static void Main(string[] args)
{
    IEnumerable<student> students = new List<student>();
    IEnumerable<person> peoples = students;
}
</person></student></student>
&#x8FD9;&#x6837;&#x5199;&#x6CA1;&#x6709;&#x62A5;&#x9519;&#xFF0C;&#x7406;&#x8BBA;&#x4E0A;IEnumerable<student>&#x662F;&#x4E00;&#x79CD;&#x7C7B;&#x578B;&#xFF0C;IEnumerable<person>&#x662F;&#x4E00;&#x79CD;&#x7C7B;&#x578B;&#xFF0C;&#x4E0D;&#x5E94;&#x8BE5;&#x80FD;&#x9690;&#x79C1;&#x8F6C;&#x6362;&#x554A;&#x3002;
</person></student>

为什么呢?因为支持协变。

协变表示能够使用比原始指定的派生类型的派生程度更大的类型。

重学c#系列——逆变和协变[二十四]
&#x4ED6;&#x4EEC;&#x7684;&#x7ED3;&#x6784;&#x5982;&#x4E0A;&#x3002;&#x56E0;&#x4E3A;student&#x662F;person&#x7684;&#x6D3E;&#x751F;&#x7C7B;&#xFF0C;IEnumerable<t>&#x539F;&#x59CB;&#x6307;&#x5B9A;&#x4E86;student&#xFF0C;&#x6D3E;&#x751F;&#x7A0B;&#x5EA6;&#x66F4;&#x5927;&#x7684;&#x7C7B;&#x578B;&#x662F;person&#xFF0C;&#x6240;&#x4EE5;IEnumerable<student>&#x5230;IEnumerable<person>&#x7B26;&#x5408;&#x534F;&#x53D8;&#x3002;
</person></student></t>

协变怎么声明呢:

public interface IEnumerable<out t> : IEnumerable
{
    //
    // &#x6458;&#x8981;:
    //     Returns an enumerator that iterates through the collection.
    //
    // &#x8FD4;&#x56DE;&#x7ED3;&#x679C;:
    //     An enumerator that can be used to iterate through the collection.
    new IEnumerator<t> GetEnumerator();
}
</t></out>

这里协变有个特点,那就是协变参数T,只能用于返回类型。

&#x539F;&#x56E0;&#x662F;&#x5728;&#x8FD0;&#x884C;&#x65F6;&#x5019;&#x8FD8;&#x662F;new List<student>()&#xFF0C;&#x8FD4;&#x56DE;&#x81EA;&#x7136;&#x662F;Student&#xFF0C;&#x90A3;&#x4E48;student &#x53EF;&#x4EE5;&#x8D4B;&#x503C;&#x7ED9;person&#xFF0C;&#x8FD9;&#x6CA1;&#x95EE;&#x9898;&#x3002;
</student>

那么协变参数T,不能用于参数呢? 是这样的。

比如 IEnumerable里面有一个方法是:

public void test(T a)&#xFF1B;
&#x5728;list<student> &#x4E2D;&#x539F;&#x672C;&#x8981;&#x4F20;&#x5165;&#x4E00;&#x4E2A;Student&#xFF0C;&#x73B0;&#x5728;&#x4F7F;&#x7528;&#x4E86;IEnumerable<person>,&#x90A3;&#x4E48;&#x5C31;&#x53EF;&#x4EE5;&#x4F20;&#x5165;person&#x3002;
</person></student>

person 要转换成student,显然是不符合的。

那么协变是这样的,那么逆变呢?

public interface ITest<in t>
{
    public void Run(T obj);
}

public class Test<t> : ITest<t>
{
    public void Run(T obj)
    {
        throw new NotImplementedException();
    }
}
</t></t></in>

然后这样使用:

static void Main(string[] args)
{
    ITest<person> students = new Test<person>();
    ITest<student> peoples = students;
    peoples.Run(new Student());
}
</student></person></person>

这里的逆变只能作用于参数。

先说一下为什么能够作用于参数,就是在运行的时候本质还是new Test(),要传递的是一个person,如果传递一个student,那么也是可以的。

然后为什么不能作用于返回值呢?

假如ITest 可以这样:

public interface ITest<in t>
{
    public T Run()
    {
    }
}
</in>

在运行时候是Test(),那么调用run返回的是person,但是赋值给了Student类型,和上面同样的问题哈。

所以协变不能作用于参数,逆变不能作用于返回值。

那么也就是说要摸只能协变,要摸只能逆变。

下面是委托中的逆变:

Action<base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<derived> d = b;
d(new Derived());
</derived>

原理就是Derived继承自Base,原本需要传入base,现在传入Derived,当然也是可以的。

之所以这么设计是一个哲学问题,那就是子类可以赋值给父类,父类能办到的子类也能办到,他们分别对应的是协变和逆变。

下一节委托。

Original: https://www.cnblogs.com/aoximin/p/16917267.html
Author: 敖毛毛
Title: 重学c#系列——逆变和协变[二十四]

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/797986/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球