前言
简单整理一下逆变和协变。
正文
什么是逆变和协变呢?
首先逆变和协变都是术语。
协变表示能够使用比原始指定的派生类型的派生程度更大的类型。
逆变表示能够使用比原始指定的派生类型的派生程度更小的类型。
这里student 继承 person。
这里这个报错合情合理。
这里可能有些刚入门的人认为,person 不是 student 的父类啊,为什么不可以呢?
一个列表student 同样也是一个列表的 person啊。
这可能是初学者的一个疑问。
但是实际情况是list<person> 是一个类型, list<student> 是一个类型。
</student></person>
所以他们无法隐式转换是正常的。
但是这样写就可以:
static void Main(string[] args)
{
IEnumerable<student> students = new List<student>();
IEnumerable<person> peoples = students;
}
</person></student></student>
这样写没有报错,理论上IEnumerable<student>是一种类型,IEnumerable<person>是一种类型,不应该能隐私转换啊。
</person></student>
为什么呢?因为支持协变。
协变表示能够使用比原始指定的派生类型的派生程度更大的类型。
他们的结构如上。因为student是person的派生类,IEnumerable<t>原始指定了student,派生程度更大的类型是person,所以IEnumerable<student>到IEnumerable<person>符合协变。
</person></student></t>
协变怎么声明呢:
public interface IEnumerable<out t> : IEnumerable
{
//
// 摘要:
// Returns an enumerator that iterates through the collection.
//
// 返回结果:
// An enumerator that can be used to iterate through the collection.
new IEnumerator<t> GetEnumerator();
}
</t></out>
这里协变有个特点,那就是协变参数T,只能用于返回类型。
原因是在运行时候还是new List<student>(),返回自然是Student,那么student 可以赋值给person,这没问题。
</student>
那么协变参数T,不能用于参数呢? 是这样的。
比如 IEnumerable里面有一个方法是:
public void test(T a);
在list<student> 中原本要传入一个Student,现在使用了IEnumerable<person>,那么就可以传入person。
</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/
转载文章受原作者版权保护。转载请注明原作者出处!