搞懂c#中的协变与逆变(针对泛型委托)

最近花一周时间阅读了《c#图解教程》的前20章,由于有一定语言基础且这本书讲解的十分通俗易懂,大部分章节都看一遍就懂了。唯独18章最后的18.10节第一遍没有看明白,反复读了两遍这一节内容后,我终于明白了c#中的协变与逆变的概念。下面讲一讲我的理解,尽量通俗易懂的为初学者解释清楚。

如果你已经看懂了《c#图解教程》第18章18.10节,那没必要再继续看了。但如果看书觉得云里雾里,或是c#初学者想弄懂这两个概念,不妨读一读此文。

我的经验是,看文字对于协变和逆变的说明很容易一头雾水,最好是先看懂两段代码分别是怎么回事,以及代码中问题的点在哪。然后重点分析两个Main函数的最后一行,通过委托执行函数时发生了什么以及为什么能够成功。顺着代码把运行的过程搞懂了,基本也就明白了协变和逆变。

 

好了,下面正式开始讲解:

c#中的可变性包括三种——协变,逆变与不变。可变性处理的是可以使用基类型替换派生类型的安全情况。这句话先记住!无论协变还是逆变,都是用于应对 使用基类型替换派生类型的安全情况 !记牢了这句话,后面理解什么情况下是协变、什么情况下是逆变就容易很多了。

 

先说什么是协变:

给定一个泛型委托"delegate T Fun1<out T>();", 协变就是:首先,将派生类类型作为泛型委托的类型参数 并产生一个委托实例。然后,把这个委托实例赋值给一个 由基类类型作为类型参数的委托对象。但要求是,这个前后产生“冲突”的类型参数T,需要在泛型委托声明时加上“out”标识,且这个T只能处于返回和输出的位置(不能出现在Fun1的参数列表中)。

这么说一定很糊涂,我们看一下简单的例子:

using System;

namespace ConsoleApp1
{

    //定以委托类型,注意out标识
    delegate T Fun1<out T>();

    //定义基类型
    class BaseClass { }

    //定义派生类
    class DerivedClass : BaseClass { }

    class Programe
    {
        //定义方法
        static DerivedClass Function1()
        {
            return new DerivedClass();
        }

        static void Main()
        {
            //通过基类类型作为模板参数定义委托实例,Function1的返回类型是DerivedClass
            Fun1<DerivedClass> del1 = Function1;

            //协变机制! 
            //del1是Fun1<DerivedClass>类型的委托对象,但del2属于Fun1<BaseClass>类型的委托对象
            Fun1<BaseClass> del2 = del1;

            //执行
            BaseClass baseCase = del2();
        }
    }

}

在通过del2()执行时,del2所属的委托类型是“delegate BaseType Fun1<out BaseType>();”, 返回的是一个BaseType类型的变量,而实际调用的函数是“ static DerivedClass Function1()”,虽然返回的是DerivedClass类型,但由于DerivedClass派生自BaseClass,将派生类对象赋值给集类对象并无问题,这就是协变。回到前面说的,“可变性处理的是可以使用基类型替换派生类型的安全情况”。协变所处理的这种情况是不是属于安全的替换?

 

再说什么是逆变:

定义一个委托类型“delegate void Fun2<in T>(T t);”, 与协变类似,逆变是首先将派生类类型作为泛型委托的类型参数 并产生一个委托实例,然后把这个委托实例赋值给一个 由基类类型作为类型参数的委托对象。

但与协变不同之处在于逆变中的逆变参数T加in标识符,同时只能出现在参数列表中。说的不清楚,还是看代码直观:

using System;

namespace ConsoleApp2
{

    delegate void Fun2<in T>(T t);

    //定义基类型
    class BaseClass{ }

    //定义派生类
    class DerivedClass : BaseClass { }

    class Program
    {
        //定义方法(省略中间class的定义)
        static void Function2(BaseClass elem) { }

        static void Main()
        {
            //通过基类类型作为模板参数定义委托实例
            Fun2<BaseClass> del1 = Function2;

            //逆变机制! 
            //del1是Fun2<BaseClass>类型的委托对象,但del2属于Fun2<DerivedClass>类型的委托对象
            Fun2<DerivedClass> del2 = del1;

            DerivedClass derivedObj = new DerivedClass();
            //执行
            del2(derivedObj);
        }
    }
}

执行del2时,由于del2属于派生类DerivedClass作为模板参数而产生的委托对象,属于"elegate void Fun2<DerivedClass>(DerivedClass t);"委托类型对应的委托实例,调用它的时候自然要传入DerivedClass类型的参数。但真正调用的函数是void Function2(BaseClass elem),所需要的参数类型是BaseClass,但由于DerivedClass是BaseClass的派生类,自然也可以安全的转换。

 

以上就是我对于c#中协变与逆变的理解,举的例子都是针对泛型委托的,其实对于泛型接口也大同小异,两段代码亲测均能通过visual studio运行成功。

欢迎大家讨论、提出自己的意见。我觉得这是《c#图解教程》前20章中最值得思考的一节内容~

自己开始写文章才发现,把一个自己理解的问题通过文章的方式讲清楚还是有点困难的。。。。

 

 

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dart 是一种支持泛型的面向对象语言,它允许我们在定义类、函数或方法时使用参数化类型泛型允许我们在不指定具体类型的情况下编写通用的代码,提高代码的重用性和安全性。 在 Dart 泛型类型变化可分为两种:类型协变(Covariance)和类型逆变(Contravariance)。 类型协变指的是可以将泛型类型的子类型赋值给父类型,这样就可以确保在使用泛型类型时不会发生类型不匹配的错误。例如,如果有一个泛型类 Animal<T>,其 T 是一个类型参数,那么 Animal<Dog> 就是 Animal<Animal> 的子类型。这样我们可以使用 Animal<Animal> 类型的变量来持有 Animal<Dog> 的实例,而不会出现类型错误。 类型逆变协变相反,指的是可以将泛型类型的父类型赋值给子类型。这样可以更灵活地使用泛型类型,更好地符合实际的业务需求。例如,如果有一个泛型类 Comparator<T>,其 T 是一个类型参数,那么 Comparator<Animal> 就是 Comparator<Dog> 的父类型。这样我们可以使用 Comparator<Dog> 类型的变量来持有 Comparator<Animal> 的实例,而不会出现类型错误。 泛型的型变在 Dart 使用通配符来表示,泛型类型协变使用 extends 关键字,逆变使用 super 关键字。例如,在声明一个泛型类型时,我们可以使用 Animal<? extends Animal> 表示协变,使用 Comparator<? super Dog> 表示逆变。这样的声明帮助我们在使用泛型类型时确保类型的正确性。 总结来说,Dart 泛型支持类型协变逆变,这样可以更灵活、更安全地使用泛型类型泛型的型变通过使用通配符、extends 和 super 关键字来表示,使得泛型类型的赋值更加灵活,能够满足不同的业务需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值