zoukankan      html  css  js  c++  java
  • 协变还是逆变,这还是个问题吗

         

           协变(Covariance)与逆变(Contravariance)是C#4.0的新特性, 初次接触逆变协变的很多人可能对这两个东西都感觉比较绕脑子,特别是逆变。

          在讲述概念之前,我们先定义两个有继承关系的类:Fruit,Apple,Apple派生自Fruit。

     public class Fruit
        {
            
    public string Name { getset; }
            
    public override string ToString()
            {
                
    return Name;
            }
            
    public virtual void GetName()
            {
                Console.WriteLine(
    "Fruit:{0}",Name);
            }


        }

        
    public class Apple:Fruit
        {
            
    public override string ToString()
            {
                
    return base.Name;
            }

            
    public override void GetName()
            {
                Console.WriteLine(
    "Apple:{0}", Name);
            }
        }

          根据继承和多态的特性,我们知道,Fruit类型变量可以指向Apple实例:

          Fruit fruit = new Apple();

          仅有泛型接口和泛型委托支持对类型参数的协变或逆变,泛型类或泛型方法不支持, 如果泛型接口或泛型委托的泛型参数声明为协变或逆变,则将该泛型接口或委托称为“变体”。

          协变:能够使用与原始指定的派生类型相比派生程度更大的类型。如我在一个委托中定义的返回值类型为Fruit 类型,按常理我们定义的委托方法的返回类型也只能是,Fruit但通过协变的委托变体,在使用这个委托变量的地方我们可以使用定义返回类型为Apple的方法,这个过程就是协变。

          逆变:能够使用与原始指定的派生类型相比派生程度更小的类型。如定义的方法参数为泛型类型为Apple的委托,但我在调用这个方法的时候可以传入泛型类型为Fruit的委托,这个过程就是逆变。  

          下面以泛型委托的协变逆变的运用来对上面概念作代码的实现。

          委托中的协变

        public delegate T Function<out T>();
        
    class Program
        {
           
            
    static void Main(string[] args)
            {
                Test(GetApple);
                Test(GetFruit);
                Console.ReadKey();

            }

            
    static void Test(Function<Fruit> function)
            {
                Fruit fruit
    = function();
                fruit.GetName();
            }

            
    static Apple GetApple()
            {
                
    return new Apple(){Name = "苹果"};
            }
            
            
    static Fruit GetFruit()
            {
                
    return new Fruit(){Name = "水果"};
            }
        }

          输出结果如下:

          Apple:苹果

          Fruit:水果

          public delegate T Function<out T>(),这是定义一个可以协变的泛型委托:out关键字表明将返回类型T声明为协变。out关键字是对应于委托中的返回类型的,如果是参数类型则使用关键字in。

          Test(Function<Fruit> function)方法中,参数类型是Function<Fruit>,但我们定义的委托的Function是可协变的,所以我们调用Test方法时,可以把和泛型类型为Fruit子类的Apple的委托Function<Apple>匹配的方法GetApple当做实参传入,从而实现委托的协变。我们发现协变的实现思想和多态很相近。

           委托中逆变:

    public delegate void Function2<in T>(T t);
        
    class Program
        {
           
            
    static void Main(string[] args)
            {
                Test2(GetApple2);
                Console.ReadKey();

            }

            
    static void GetApple2(Fruit f)
            {
                f.GetName();
            }
            
    static void Test2(Function2<Apple> function2)
            {
                function2(
    new Apple() { Name = "苹果" });
            }
        }

           输出结果为:

           Apple:苹果

            public delegate void Function2<in T>(T t);定义一个能够对T类型逆变的泛型委托。

            Test2(Function2<Apple> function2) 方法中我们声明的参数类型为Function2<Apple>,但我们调用Test2方法的时候传入的是和泛型类型为Apple父类Fruit类型的委托Function2<Fruit>匹配的方法GetApple2(Fruit f),由此实现泛型委托的逆变。

           我们发现从C#2.0开始,委托就支持协变和逆变,但不支持关键字in和out,在上面泛型委托的协变逆变演示代码中,我们去掉关键字in和out,程序还是能正常运行。但在C#4.0之前,泛型接口是不支持协变逆变的,因为当时还没有在泛型中引入in和out关键字(如果泛型类型T使用in,则T类型只能用于方法的参数,使用out,则T类型只有用作方法的返回类型),一个泛型接口中定义的方法返回类型和参数类型都可以是泛型类T,支持协变逆变就无法保证类型安全,.NET4.0后,使用in和out关键字,限制了接口中方法参数和返回类型,类型安全得到了确保,所以泛型接口可以支持协变逆变,于是很多接口在.NET 4.0进行了升级,如IEnumerable<T>重新声明为IEnumerable<out T>。

          接口中的协变

     IEnumerable<Apple> d = new List<Apple>() { new Apple() { Name = "Apple1" }, new Apple() { Name = "Apple2" } };
                IEnumerable
    <Fruit> b = d;

          接口中的逆变

     

          Action<object> action1 = o => { };
          Action
    <string> action2 = action1;//逆变

        

          总结

          1)仅有泛型接口和泛型委托支持对类型参数的协变或逆变,泛型类或泛型方法不支持;

          2)值类型不参与协变或逆变;

          3)通过逆变可以实现算法的复用;

          4)不管是协变还是逆变,都是在类型安全的情况进行的;

          5)协变对应返回类型,逆变对应参数类型;

          参考文章:

          Artech:C# 4.0新特性-"协变"与"逆变"以及背后的编程思想

          装配脑袋:.NET 4.0中的泛型协变和反变

    作者:边写边唱

    文章出处:http://www.cnblogs.com/zoupeiyang

    专注于用自助终端技术实现互联网+,有兴趣朋友欢迎关注   捷思科技

  • 相关阅读:
    Google ObjectiveC Style Guide
    FlvDownloader 2.2发布
    在C#中实现关机
    在.net 2.0/3.0程序中使用扩展方法
    Boost智能指针——scoped_ptr
    二叉查找树
    用C#调用ffmpeg实现媒体类型转换(1)
    FlvDownloader v2.21发布
    发布一款ICO图标和PNG批量转换工具
    在.net中创建外接程序
  • 原文地址:https://www.cnblogs.com/zoupeiyang/p/2130317.html
Copyright © 2011-2022 走看看