你的位置:首页 > ASP.net教程

[ASP.net教程]一个简单例子理解C#的协变和逆变


 

关于协变逆变,SolidMango的解释是比较可取的。有了协变,比如,在需要返回IEnumerable<object>类型的时候,可以使用IEnmerable<string>来替代;有了逆变,比如,在需要接收IComparable<string>类型形参方法中,可以使用IComparable<object>类型实参来替代。

 

协变

 

先来体会协变。有2个具有继承关系的父类和子类。

 

  public class Animal
  {
    public string Name { get; set; }
  }
  public class Dog : Animal
  {
    public Dog(string dogName)
    {
      Name = dogName;
    }
  }

 

现在有一个帮助类的方法的形参类型是父类集合IEnumerable<Animal>。

 

  public class MyHelper
  {
    public void PrintAnimalNames(IEnumerable<Animal> animals)
    {
      foreach (var animal in animals)
      {
        Console.WriteLine(animal.Name);
      }
    }
  }

 

有了协变,可以在PrintAnimalNames方法中传入IEnumerable<Dog>类型的实参替代IEnumerable<Animal>类型。

 

    static void Main(string[] args)
    {
      List<Dog> dogs = new List<Dog>()
      {
        new Dog("小狗petty"),
        new Dog("小狗lily")
      };
      //协变
      IEnumerable<Animal> animals = dogs;
      MyHelper myHelper = new MyHelper();
      myHelper.PrintAnimalNames(animals);
      Console.ReadKey();
    }


可见,在方法中基于基类接口类型的形参,调用该方法的时候可以传入派生类接口类型的实参。       

 

逆变


再来体会逆变。依然是2个具有继承关系的父类和子类。

 

  public class Animal 
  {
    public string Name { get; set; }
    public int Age { get; set; }
  }
  public class Cat : Animal
  {
    public Cat(string catName, int catAge)
    {
      Name = catName;
      Age = catAge;
    }
  }

 

现在,我们想比较基类Animal的两个实例,为此,有必要专门写一个类让他实现IComparer<Animal>接口。

 

  public class AnimalSizeComparator : IComparer<Animal>
  {
    public int Compare(Animal x, Animal y)
    {
      if (x != null && y != null)
      {
        if (x.Age > y.Age)
        {
          return 1;
        }
        else if (x.Age == y.Age)
        {
          return 0;
        }
        else
        {
          return -1;
        }
      }
      else
      {
        return -1;
      }
    }
  }

 

在帮助类中的方法中,针对Cat进行比较,方法接收IComparer<Cat>类型的形参。

 

  public class MyHelper
  {
    public void CompareCats(IComparer<Cat> catComparer)
    {
      var cat1 = new Cat("小猫1",1);
      var cat2 = new Cat("小猫2",2);
      if (catComparer.Compare(cat2, cat1) > 0)
      {
        Console.WriteLine("小猫2胜出");
      }
      else
      {
        Console.WriteLine("小猫1胜出");
      }
    }
  }  

 

有了逆变,客户端调用MyHelper的CompareCats方法时,可以传入IComparer<Animal>类型的实参。

 

      IComparer<Animal> animalComparer = new AnimalSizeComparator();
      MyHelper myHelper = new MyHelper();
      myHelper.CompareCats(animalComparer);
      Console.ReadKey(); 

 

可见,在方法中基于派生类接口类型的形参,调用该方法的时候可以传入基类接口类型的实参。 

      

总结:在本篇的场景中,派生类接口替代父类接口,称之为协变;父类接口代替派生类接口,称之为逆变。