zoukankan      html  css  js  c++  java
  • .NET 4 泛型中的Covariance 和 Contravariance

    在介绍.NET 4泛型的covariance和contravariance之前,先介绍下编程语言类型系统的variance的概念。简单的说covariance使得你能够用一个更具体的类来替代一个本该父层的类。在C#中,引用类型的数组是covariant的,这是从当时的Java中学来的特性,例如:

    namespace variance2
    {
        public class Animal { }
        public class Dog : Animal { }
        class Program
        {
            static void Main(string[] args)
            {
                Animal[] animals = new Dog[10];
                animals[0] = new Animal();
            }
        }
    }

    当给animals数组初始化的时候,可以使用它的子类Dog,这里是covariance的。这段代码可以编译通过,但是运行的时候会报如下错误:

    Attempted to access an element as a type incompatible with the array.

    因为一旦给数组赋值了Dog,实际上它就是一个Dog数组,所以不能再给他赋值Animal。一段能编译通过的代码再运行时发生类型错误并不是好事情,所以在.Net 2引入泛型的时候.net的泛型是invariant的。

    List<Animal> listAnimals = new List<Dog>();

    这样是不能通过编译的。

    List<Dog> listDogs = new List<Dog>();
    listDogs.Add(new Animal());
    这样也不能通过编译。
    .NET2 这样做避免了一些问题,但同时也带来了一些问题,例如:
     
    List<Dog> listDogs = new List<Dog>();
    IEnumerable<Animal> enumAnimals = listDogs;

    这样是不能编译通过的。实际上,这种编程场景很常见,而且事实上这是类型安全的,因为通过IEnumerable接口,我们只能从enumAnimals中获取值,而不能给他赋值。

    下面举一个contravariance的例子:

    public class Animal
       {
           public int Weight
           {
               get;set;
           }
    
           public string Name
           {
               get;set;
           }
    
           public Animal(string name,int weight)
           {
               Name=name; Weight=weight;
           }
       }
       public class Dog : Animal 
       {
           public Dog(string name,int weight):base(name,weight)
           {
           }
       }
    
       public class WeightComparer : IComparer<Animal>
       {
           public int Compare(Animal x, Animal y)
           {
               return x.Weight - y.Weight;
           }
       }

    给动物类加上一个重量的属性,并且实现一个根据重量排序的IComparer类。

    class Program
    {
        static void Main(string[] args)
        {
            WeightComparer comparer = new WeightComparer();
            List<Animal> animals = new List<Animal>();
            animals.Add(new Animal("Dog", 4));
            animals.Add(new Animal("Mouse", 1));
            animals.Add(new Animal("Tiger",44));
            animals.Sort(comparer);   //works fine
            animals.ForEach(e=>Console.WriteLine(e.Name+" "+e.Weight));
    
            List<Dog> dogs = new List<Dog>();
            dogs.Add(new Dog("DogA", 12));
            dogs.Add(new Dog("DogB", 10));
            dogs.Sort(comparer);     //compile error
            dogs.ForEach(e => Console.WriteLine(e.Name + " " + e.Weight));
        }
    }

    注意如果在.net 2中运行,第一段程序是可以正常运行的。第二段程序会导致编译错误,这里本应该的对象是 IComparer<Dog>,但是实际上传给他的是ICompaer<Animal>,是contravariance,这在.net 2中是不允许的,实际上,这种情况也是类型安全的。

    .Net 4中在泛型参数前可以加上in或者out关键字。in 关键字可以使参数变成contravariant的。out 关键字可以使参数变成covariant的。在.net 类库中,很多泛型接口和委托的声明已经改变。例如,使用out关键字的有:

    IEnumerable(Of T), IEnumerator(Of T), IQueryable(Of T) IGrouping(Of TKey, TElement)

    使用in关键字的有:

    IComparer(Of T), IComparable(Of T), IEqualityComparer(Of T).

    因此,上文中的涉及到泛型接口的代码在.NET4中都是可以运行成功的。

    .Net4中还有一些常用的委托也使用了in,out关键字来声明泛型参数。例如:

    Action<in T> 委托,其声明为:

    public delegate void Action<in T>( T obj )
    可以使用contravariance:

    static void Main(string[] args)
    {
        Action<Animal> animal = (obj) => { Console.WriteLine(obj.GetType().ToString()); };
        Action<Dog> dog = animal;
        dog(new Dog("Test",1));
    }
    
    

    再例如,Func<T,TResult>委托,声明为:

    public delegate TResult Func<in T, out TResult>( T arg )

    传入参数可以是contravariant,返回值可以是covariant的。

    public class Type1 {}
    public class Type2 : Type1 {}
    public class Type3 : Type2 {}
    
    public class Program
    {
        public static Type3 MyMethod(Type1 t)
        {
            return t as Type3 ?? new Type3();
        }
    
        static void Main() 
        {
            Func<Type2, Type2> f1 = MyMethod;
    
            // Covariant return type and contravariant parameter type.
            Func<Type3, Type1> f2 = f1;
            Type1 t1 = f2(new Type3());
            Console.WriteLine(t1.GetType());
        }
    }
    
    

    运行结果是 Type3.

  • 相关阅读:
    Angular
    Angular
    Angular
    Angular
    Angular
    Angular
    Angular
    springsecurity 源码解读 之 RememberMeAuthenticationFilter
    springsecurity 源码解读之 AnonymousAuthenticationFilter
    springsecurity 源码解读之 SecurityContext
  • 原文地址:https://www.cnblogs.com/yinzixin/p/2193752.html
Copyright © 2011-2022 走看看