c# 泛型协变逆变学习

最近在项目开发当中使用泛型委托Func较多,查看Func的定义就会发现Func的入参都会都会标记上in,出参都会标记上out.

in 和out和泛型类型实参有关, 其中in代表逆变,out代表协变.自己协变和逆变在设计接口或者委托的时候也没有定义过,

因此就详细了解一下其用法.

一.关于协变和逆变

在c# 4.0后,泛型类型参数分为以下三种请情况

1.不变量   这带便泛型参数类型参数不能更改

2.协变量  泛型类型参数可以从一个类更改为它的某个基类.c#中使用out关键字标记协变量形式的泛型类型参数.

协变量泛型类型参数只能出现在输出位置,作为方法的返回类型

3.逆变量  泛型类型参数可以从一个类更改为它的某个派生类.c#中使用in关键字标记逆变量形式的泛型类型参数

逆变量泛型类型参数只能出现在输入位置,作为方法的入参

比如在上述截图的代码中,T1和T2作为委托的入参,Tresult作为委托的出参

 二  引入协变逆变

我们看如下代码

 class Program
    {
        static void Main(string[] args)
        {

            //1.
            {
                //List<A> list = new List<B>();  error   List<B>并不是List<A>的子类
            }

            //2.
            {
                IEnumerable<A> list = new List<B>();
              // IEnumerable<B> list1 = new List<A>();// ERROR

            }

            //3.
            {
                Func<A> func = null;
                Func<B> func1 = null;
                func = func1;
                // func1 = func;  ERROR
            }

            //4.
            {
                Action<A> action = null;
                Action<B> action1 = null;
                action1 = action;
              //  action = action1;  error
            }

        }

    }
    public interface A
    {

    }

    public class B : A
    {

    }

(1)其中第一个注释部分很明显示编译不过的,因为前后关系并不是继承关系.

(2)第二个编译器能编译通过,第2个不能编译通过,

我们看下 IEnumerable接口的定义

public interface IEnumerable<out T> : IEnumerable

泛型类型前面标识为out

(3)第三个泛型委托中,第1个能编译通过,第二个不能

我们看下Func的定义

public delegate TResult Func<out TResult>();

泛型类型前面标识为out

(4)第四个泛型委托中,第1个能编译通过,第2个不能

我们看下Action的定义

public delegate void Action<in T>(T obj)

泛型类型前面标识为in

以上代码展示了协变和逆变的基本特性

即协变时,泛型类型参数可以从一个类更改为它的某个基类,且该类型仅可作为出参

逆变时,泛型类型参数可以从一个类更改为它的某个派生类,且该类型仅可作为入参

总结

为什必须显示使用in或则out标记类型类型参数,因为我们在编码过程中,需要我们自己去定义协议,

明确告诉编译器允许什么,那么在编译器就能识别出你的泛型类型参数到底能存在什么样的位置上,

若果不用定这个协议,那么编译器在编译时不能识别出,在运行时将会抛出未知的错误.

协变和逆变在设计框架时会有较大的帮助,后续会继续分享在设计框架时协变逆变的用法.

 

原文地址:https://www.cnblogs.com/xxue/p/9863805.html

时间: 2024-08-02 19:38:49

c# 泛型协变逆变学习的相关文章

解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有区别. 不变.协变和逆变 在.Net4.0之前,泛型接口是不变的..Net4.0通过协变和逆变为泛型接口和泛型委托增加了重要的扩展. 注:本书总体非常好,但在协变和逆变方面,我认为是有缺陷的.我一直偏好通过读书籍来了解技术,而不是逛论坛,但协变和逆变的问题我研究了本书多次,都没搞懂而放弃了,反正平时

泛型之逆变和协变总结

泛型之逆变和协变总结 c# 泛型 逆变 协变 变的概念 协变(Foo<父类> = Foo<子类> ) 逆变(Foo<子类> = Foo<父类>) 逆变与协变的相互作用 变的概念 //父类 = 子类 string str = "string";  object obj = str;//变了  协变(Foo<父类> = Foo<子类> ) //泛型委托: public delegate T MyFuncA<T&g

Java泛型的逆变

在上篇<Java泛型的协变>这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码清单1 /** * * 描 述:Exp3使用逆变<br/> * 作 者:jiaan.gja<br/> * 历 史: (版本) 作者 时间 注释 <br/> * @param itemList */ public void doDecorate3(List<? super

ios开发ios9新特性关键字学习:泛型,逆变,协变,__kindof

一:如何去学习?都去学习什么? 1:学习优秀项目的设计思想,多问几个为什么,为什么要这么设计,这么设计的好处是什么,还能不能在优化 ,如何应用到自己的项目中 2:学习优秀项目的代码风格,代码的封装设计思想,为什么要这么设计,这么设计的好处是什么,还能不能在优化 ,如何应用到自己的项目中,每行代码都要用心去写,每一行代码都要力求使最简洁的 3:学习别人遇到问题是如何分析问题,解决问题的方法是什么 4:遇到新东西应该如何去学习:1:先研究要学习的东西作用是什么 ,有什么好处  2:如何使用:具体的语

C# 泛型的协变与逆变

那么先讲讲什么是"泛型类协变"你可以这样理解,正常的变换 好比object = String 这样的表达式一样协变主要是向上安全的变换,但是需要根据继承链条一层一层 的向上变化 而不需要进行转换因为是隐式转换,先上一层继承类的代码 不过使 用最容易的理解的,你认为是拆箱与装箱也没什么太大的出入 但是指泛型上的 [csharp] view plaincopy public class Animal // 动物 { } public class Dog : Animal // 狗 { }

c#-泛型、协变、逆变

泛型简单介绍: 可以使用泛型声明的元素:类.接口.方法.委托 泛型之前:泛型之前使用object封装不同类型的参数,缺点:性能差.运行时判断类型(不安全)...泛型是在编译期间转为实际类型副本,所以性能好,还可以使用约束对泛型进行约束 泛型约束:约束泛型类型必须满足约束.使用泛型约束后,可以像使用约束类型的方式使用泛型变量 约束种类:where T:class//引用类型约束where T:new()//无参构造函数约束,加了此约束后可以使用无参构造函数创建实例:T t = new T();wh

Java进阶知识点2:看不懂的代码 - 协变与逆变

要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那么我们可以在任何时候使用MochaCoffee类的引用去替换Coffee类的引用(重写函数时,形参必须与重写函数完全一致,这是一处列外),而不会引发编译错误(至于会不会引发程序功能错误,取决于代码是否符合里氏替换原则). 简而言之,如果B类是A类的派生类,那么B类的引用可以赋值给A类的引用. 赋值的方式最常见

这一次,终于弄懂了协变和逆变

一.前言 刘大胖决定向他的师傅灯笼法师请教什么是协变和逆变.   刘大胖:师傅,最近我在学习泛型接口的时候看到了协变和逆变,翻了很多资料,可还是不能完全弄懂. 灯笼法师:阿胖,你不要被这些概念弄混,编译器可不知道你说的什么协变逆变.这个问题,首先你得弄懂什么叫类型的可变性. 刘大胖:可变性? 二.可变性 灯笼法师:对,可变性是以一种类型安全的方式,将一个对象作为另一对象来引用.虽然是可变,但其实对象的引用地址是不会变的,只是忽悠下编译器. 刘大胖:师傅说的将一个对象作为另一对象来引用?这不就是继

EF中逆变和协变

EF中的增删改查: 实现步骤: 1.声明一个EF的上下文. bjhksjEntities dbContext = new bjhksjEntities(); 2.声明一个实体. HKSJ_USERS user = new HKSJ_USERS(); user.LoginName = "ssss"; user.Mail = "ssss"; user.PassWord = "ssss"; user.Plane = "ssss";