zoukankan      html  css  js  c++  java
  • .Net 4.0中支持的更加完善了协变和逆变

    协变和逆变,这两个词的翻译实在很难表达出他们的真实含义。其实他们是继承和多态的衍生物,而且在.Net 1.0 和2.0中都提供了某种程度的支持,只是在.Net 4.0中支持的更加完善了。

    简单说来,协变和逆变就是希望支持更多情况的隐式类型转换,使得我们的编程更加方便,而通常来说只有具备继承关系的两个对象才可以发生隐式类型转换,如Base b=new Derived(). 协变和逆变则使得更多的类型之间可以发生隐式类型转换,如通过协变以下代码可以正常工作:

               Func<Derived> dFunc=GetDFunc();           Func<Base> bFunc = dFunc;           IEnumerable<Derived> dEnum=GetDEnum();           IEnumerable<Base> bEnum = dEnum;

    那么我们为什么需要这种功能呢?让我们从一个多态的例子开始:

         abstract class Animal     {         internal abstract void Eat();     }     abstract class Mammal : Animal     {     }     class Tiger : Mammal     {         internal override void Eat()         {             Console.WriteLine("Tiger eat");         }     }
    class Program{    static void Main(string[] args)    {        Tiger tiger = new Tiger();        FeedAnimal(tiger);    }    static void FeedAnimal(Animal animal)    {        animal.Eat();    }}

    虽然FeedAnimal方法接受的参数类型为Animal ,但是当我们传入一个Tiger 的实例,方法能够编译通过,并得到正确的执行。这是一个典型的多态运用。之所以能够这么做是因为Tiger 继承于Animal ,在FeedAnimal方法中对Animal对象的各种操作,Tiger 对象同样支持,而不会发生调用了animal的某个方法A,而tiger中不存在A方法的情况。 那么如果我们把这一原则推而广之,就出现了协变这一概念。

           static void FeedAnimal(Func<Animal> animalCreator)       {           var animal=animalCreator();           animal.Eat();       }       static Tiger CreateTiger()       {           return new Tiger();       }

    现在我们让FeedAnimal方法接受一个能够返回Animla的delegate,而同时又创建了一个能够返回Tiger的方法。按照之前的推理,FeedAnimal方法无非就是对delegate中返回的Animal对象进行各种操作,那么如果给它一个返回Tiger对象的delegate,也应该能够正常的工作。所以下面这段代码是可以在.NET 2.0中工作的。

    class Program{    static void Main(string[] args)    {        FeedAnimal(CreateTiger);    }}

    这就是所谓的协变,我们希望两个对象之间,除了继承关系外也能做隐式类型的转换,因为我们认为这种转换是合理的,是类型安全的。

    大家可能很奇怪,如果.Net 2.0就已经支持了以上的代码,那么4.0又搞了些什么?

    先来看看下面的代码:

      
    class Program{    static void Main(string[] args)    {        Tiger tiger = new Tiger();        FeedAnimal(tiger);    }    static void FeedAnimal(Animal animal)    {        Console.WriteLine("Feed Animal");    
        }
     
        static void FeedAnimal(Mammal mammal)
        {
            Console.WriteLine("Feed Mamal");  
        } }
     

    以上这段代码的输出结果是 Feed Mammal,当碰到两个匹配的重载方法时,.Net编译器选择了形参类型在继承树上更接近自己的方法。可是如果我们把Animal改成Func<Animal> 以下代码就编译出错了,编辑器不知道选择哪个方法作为匹配。

    class Program{    static void Main(string[] args)    {        FeedAnimal(CreateTiger);    }
        static Tiger CreateTiger() { return new Tiger(); }   
        static void FeedAnimal(Func<Animal> animalCreator)    {        var animal=animalCreator();        animal.Eat();    }    static void FeedAnimal(Func<Mammal> animalCreator)    {        var animal = animalCreator();        animal.Eat();    }

    究其根本,在C# 2.0中你无法实现以下delegate间的隐式类型转换

    Func<Tiger> tFunc = CreateTiger;Func<Animal> aFunc = tFunc;

    而C# 4.0, 使得上述代码能够成功运行,之前的两个方法他也能够做出正确的选择。这是通过out关键字实现的。 注意Func<TResult> 这个delegate在C# 2.0和4.0中的定义是不同的:

         //C# 2.0     public delegate TResult Func<TResult>();     //C# 4.0     public delegate TResult Func<out TResult>();

    out关键字表示该类型用于返回值,而返回值可以发生协变,因为一个方法如果能够接受一个返回Animal的delegate,那么他必然也可以接受一个返回Tiger的delegate,因为他对animla的操作同样可以作用于tiger上。

    既然delegate能够支持协变,那么interface也应该给予支持,因为它无非就是多个delegate罢了。

    以下代码在C# 2.0中式不能通过编译的, 这样看来interface还不如delegate支持的好。

    static void Main(string[] args){    List<Tiger> tigers = new List<Tiger>();    FeedAnimals(tigers);          }static void FeedAnimals(IEnumerable<Animal> animals){    foreach (var animal in animals)        animal.Eat();}

    下面的代码也就更加不行了:

    List<Tiger> tigers = new List<Tiger>();
    IEnumerable<Animal> animlas = tigers;

    到了C# 4.0中,由于out关键字的缘故,以上两段代码都能正常工作了。而IEnumerable<T>的定义也被改为

         public interface IEnumerable<out T> : IEnumerable     {                              IEnumerator<T> GetEnumerator();     }

    之前举得例子都是描述协变的,其实逆变无非就是扩大了输入参数的类型转换的可能,不过方向是反的。

      static void FeedAnimal(Animal animal)  {      animal.Eat();  }  static void Execute(Action<Mammal> tAct)  {      tAct(new Tiger());  }

    在Execute方法中, 我们接受一个类型为Action<Mammal> 的delegate,我们在使用这个delegate时,可以传入各种类型的Mammal,比如Tiger, Lion。如果我们传入的delegate能够作用于Animal对象,那么他自然可以作用于Tiger, Lion。 所以我们可以传入一个输入类型更抽象的delegate。因此,在逆变的支持下我们可以写出以下代码。

          static void Main(string[] args)      {          Action<Animal> aAct = FeedAnimal;          Action<Mammal> mAct = aAct;                    Execute(aAct);      }

    参考资料:

    eric's series on covariance and contravariance 


           快速评论通道--您对本文的宝贵意见:
           
    感谢您的鼓励和批评,它将是我进步的动力

  • 相关阅读:
    C#的类,构造函数以及Array阵列的数据填充与绑定
    子菜单显示了,就不想隐藏了
    获取DataTable选择第一行某一列值
    两个dropDownList和一个GridView的选择与显示
    127.0.0.1SQLEXPRESS连接异常
    Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive.
    The system cannot find the file specified
    Handler "BlockViewHandler" has a bad module "ManagedPipelineHandler" in its module list
    The Web server is configured to not list the contents of this directory.
    分布式监控系统Zabbix-图形集中展示插件Graphtree安装笔记
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/1762165.html
Copyright © 2011-2022 走看看