一、协变和抗变的基本概念

      在.NET4之前,泛型接口是不变的。.NET4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变指对参数和返回值的类型进行转换。例如,可以给一个需要Shape参数的方法传入一个Rectangle参数吗?下面用实例说明这些扩展的优点。

      在.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape基类。声明Display()方法是为了接受Shape类型的对象作为其参数:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void Display(Shape o) { }  

      现在可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以Rectangle满足Shape的所有要求,编译器接受这个方法调用:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Rectangle r = new Rectangle() { Width = 5, Height = 2.5 };  
  2. Display(r);  

       方法的返回类型是抗变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来是可行的:如果一个方法向GetRectangle()方法那样返回一个Rectangle,

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public Rectangle GetRectangle();  

      就可以把结果赋予某个Shape:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Shape s = GetRectangle();  

      在.NET Framework 4版本之前,这种行为方式不适用于泛型。在C#4中,扩展后的语言支持泛型接口和泛型委托的协变和抗变。下面开始定义Shape基类和Rectangle类:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class Shape  
  2. {  
  3.     public double Width { get; set; }  
  4.     public double Height { get; set; }  
  5.   
  6.     public override string ToString()  
  7.     {  
  8.         return string.Format("Width: {0}, Height: {1}.", Width.ToString(), Height.ToString());  
  9.     }  
  10. }  
  11.   
  12. public class Rectangle : Shape  
  13. {  
  14.   
  15. }  

二、泛型接口的协变

       如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口IIndex与类型T是协变的,并从一个只读索引器中返回这个类型:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public interface IIndex<out T>  
  2. {  
  3.     T this[int index] { get; }  
  4.     int Count { get; }  
  5. }  

      IIndex<T>接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class RectangleCollection : IIndex<Rectangle>  
  2. {  
  3.     private readonly Rectangle[] _data = new Rectangle[3]  
  4.                              {  
  5.                                  new Rectangle{Height = 2,Width = 5},  
  6.                                  new Rectangle{Height = 3,Width = 4},  
  7.                                  new Rectangle{Height = 4,Width = 5},  
  8.                              };  
  9.   
  10.     public static RectangleCollection GetRectangles()  
  11.     {  
  12.         return new RectangleCollection();  
  13.     }  
  14.   
  15.     public Rectangle this[int index]  
  16.     {  
  17.         get  
  18.         {  
  19.             if (index < 0 || index > _data.Length)  
  20.                 throw new ArgumentOutOfRangeException("index");  
  21.             return _data[index];  
  22.         }  
  23.     }  
  24.   
  25.     public int Count { get { return _data.Length; } }  
  26. }  
    RectangleCollection.GetRectangles()方法返回一个实现IIndex<Rectangle>接口的RectangleCollection类,所以可以把返回值赋予IIndex<Rectangle>类型中的变量rectangle。因为接口是协变的,所以也可以把返回值赋予IIndex<Shape>类型的变量。Shape不需要Rectangle没有提供的内容。使用shapes变量,就可以在for循环中使用接口中的索引器和Count属性:
[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static void Main(string[] args)  
  2.  {  
  3.      //协变  
  4.      IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();  
  5.      IIndex<Shape> shapes = rectangles;  
  6.      for (var i = 0; i < shapes.Count; i++)  
  7.      {  
  8.          Console.WriteLine(shapes[i]);  
  9.      }  
  10.  }  
    运行结果如下:

 

三、泛型接口的抗变

       如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public interface IDisplay<in T>  
  2. {  
  3.     void Show(T s);  
  4. }  

      ShapeDisplay类实现了IDisplay<Shape>,并使用Shape对象作为输入参数:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class ShapeDisplay : IDisplay<Shape>  
  2. {  
  3.     public void Show(Shape s)  
  4.     {  
  5.         Console.WriteLine("{0} Width: {1}, Height: {2}.", s.GetType().Name, s.Width, s.Height);  
  6.     }  
  7. }  

      创建ShapeDisplay的一个新实例,会返回IDisplay<Shape>,并把它赋予shapeDisplay变量。因为IDisplay<T> 是抗变的,所以可以把结果赋予IDisplay<Rectangle>,其中Rectangle派生自Shape。这次接口的方法只能把泛型类型定义为输入,而Rectangle满足Shape的所有要求:

 

[csharp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private static void Main(string[] args)  
  2. {  
  3.     //协变  
  4.     IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();  
  5.     IIndex<Shape> shapes = rectangles;  
  6.     for (var i = 0; i < shapes.Count; i++)  
  7.     {  
  8.         Console.WriteLine(shapes[i]);  
  9.     }  
  10.   
  11.     //抗变  
  12.     IDisplay<Shape> shapeDisplay = new ShapeDisplay();  
  13.     IDisplay<Rectangle> rectangleDisplay = shapeDisplay;  
  14.     rectangleDisplay.Show(rectangles[0]);  
  15. }  

 

  运行结果如下:
posted on 2015-07-07 13:44  初夏0112  阅读(177)  评论(0)    收藏  举报