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


    前言

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

    正文

    什么是逆变和协变呢?

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

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

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

    这里student 继承 person。

    这里这个报错合情合理。

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

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

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

    但是实际情况是list 是一个类型, list 是一个类型。
    

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

    但是这样写就可以:

    static void Main(string[] args)
    {
    	IEnumerable students = new List();
    	IEnumerable peoples = students;
    }
    
    这样写没有报错,理论上IEnumerable<Student>是一种类型,IEnumerable<Person>是一种类型,不应该能隐私转换啊。
    

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

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

    他们的结构如上。因为student是person的派生类,IEnumerable<T>原始指定了student,派生程度更大的类型是person,所以IEnumerable<Student>到IEnumerable<Person>符合协变。
    

    协变怎么声明呢:

    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 GetEnumerator();
    }
    

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

    原因是在运行时候还是new List(),返回自然是Student,那么student 可以赋值给person,这没问题。
    

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

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

    public void test(T a)
    在list<Student> 中原本要传入一个Student,现在使用了IEnumerable<person>,那么就可以传入person。
    

    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();
    	}
    }
    

    然后这样使用:

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

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

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

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

    假如ITest 可以这样:

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

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

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

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

    下面是委托中的逆变:

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

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

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

    下一节委托。

  • 相关阅读:
    小程序中如何查看指定会员的所有订单?
    “新KG”视点 | 漆桂林——知识图谱和大语言模型的共存之道
    150.逆波兰表达式求值
    【云原生】设备云之基于FlexManager的C#SDK开发案例代码
    Godot 官方2D C#重构(1):雪花碰撞
    1032 Sharing
    【分享】教你加速访问GitHub,进来学!
    计算机毕业设计选题推荐-springboot 企业在线培训系统
    杰理之SPP 连接和断开事件处理【篇】
    面试题-多线程-解释什么是死锁( deadlock )
  • 原文地址:https://www.cnblogs.com/aoximin/p/16917267.html