• 抗变与协变


    前言

      工作一年了,平时也喜欢看看书,逛逛园子;但说到写博,还真的没有,说到底,只有一个字:懒!现在想改掉这个“毛病”了,希望多把平时工作学习到的知识和遇到的问题记录下来,一是可以梳理自己的思路,加深理解;二是可以向更多的朋友学习和分享;三是可以锻炼自己的写作水平;可谓百利而无一害!

      平时偶尔会遇到一些小问题,很多时候都是查了记住,或者简单写写笔记,当时理解就过了,没有形成文档,等过段时间又遇到同样的问题,又要重新去查去理解,甚是麻烦。希望以后把这些东西写成文章,尽管可能是很小的问题,也当做笔记记录。关于c#的一些概念、语法或者规范,就记录在【c#笔记】。由于不是初学边学边记,所以没有一定的时间和学习顺序,只是平时遇到觉得有必要,就记录下来。

    一、遇到问题

      工作是基于.net3.5开发,实际过程遇到一个问题。假设我们有一个 Base 类,一个 Derived 类,Derived 继承了 Base。如下:

        class Base
        {
    
        }
        class Derived : Base
        {
    
        }  

      当我用IEnumerable<Base> 作为形参,List<Derived> 作为实参时,发现编译出错了!原本父类作为形参,传递子类是再正常不过的,但在泛型中确编译不通过。

    二、探究问题

      通常我们在设计参数和返回值都有一个原则,参数要尽可能“泛”,返回值要尽可能的“细”。泛,指得是用接口或者父类作为参数,这样可以接收更多的参数类型;细,指的是返回具体类型,这样可以更好说明方法的作用。

      举个例子:

            string[] strs = new string[] { "hello", "word" };
    
            //这样的缺点是数组就传递不了了,还要调用一次 ToList()
            static void Test_1(List<string> list)
            {
    
            }
    
            //正确的做法,应该用IEnumerable<T>
            static void Test_2(IEnumerable<string> list)
            {
    
            }  

      可见,参数的“泛”可以提供更大的灵活性。

      接着就进入本次的主题:抗变与协变。需要说明的是,抗变与协变是在4.0开始支持的。假设有一个方法需要Derived集合作为参数,那么基于上面的原则,我们会这样设计:

      static void TestIn(IEnumerable<Base> bases)
      {
    
      }
    

      接着我们向下面这样调用,在3.5下就会发现编译不通过,提示无法将 List<Derived>转换为IEnumerable<Base>。

     List<Derived> listIn = new List<Derived>();
     TestIn(listIn);
    

      同样的代码,我们拿到4.0下,发现编译通过了。比较 IEnumerable泛型接口,我们发现4.0下的定义为:  

     public interface IEnumerable<out T> : IEnumerable
    

      发现多了 out 关键字,这就是协变。msdn对于类型参数的解释是:out 要枚举的对象的类型。该类型参数是协变的。即可以使用指定的类型或派生程度更高的类型。

      我们可以这样理解协变,参数的类型就是协变的,父类用子类代替,也就是子类当父类使用。

      理解协变后,抗变就好理解了。函数的返回值就是抗变的,子类用父类代替,也就是父类当子类使用。在非泛型的情况下,我们可以这样接收方法的返回值:  

      object obj = Test_3();
    
      static string Test_3()
      {
        return "hello world";
      }  

      当然,我们觉得这样调用也应该是可以的:  

      IEnumerable<Base> listOut = TestOut();
      static IEnumerable<Derived> TestOut()
      {
        return new List<Derived>();
      }
    

      在3.5下,这样同样会编译错误。4.0下就没有问题。

    三、总结

       协变与抗变的概念其实我们经常遇到(参数协变、返回值抗变),而且我们也会习惯的这样设计。但对于泛型,.net 到了4.0才提供这样的支持,这为泛型的使用提供了更大的灵活性。

      ok,实际我们不怎么需要去理解概念性的东西,知道原理和理解怎么使用即可。以上是我的个人理解,如果有朋友想要更深入的理解,可以参见msdn。

  • 相关阅读:
    工商银行:应用多k8s集群管理及容灾实践
    鸿蒙轻内核源码分析:掌握信号量使用差异
    云图说|ASM灰度发布,让服务发布变得更敏捷、更安全
    解读多云跨云下的容器治理与实践
    互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”
    带你深入理解Java的IO到底是个啥
    毕业季offer怎么拿?收下这份非典型求职面试指南
    CKEditor 5 摸爬滚打(一)—— 从零构建定制化工程项目
    说起来你可能不信,一个正则就能让页面卡死
    sklearn中的pipeline的创建与访问
  • 原文地址:https://www.cnblogs.com/4littleProgrammer/p/4782339.html
Copyright © 2020-2023  润新知