一、协变和抗变的基本概念
在.NET4之前,泛型接口是不变的。.NET4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变指对参数和返回值的类型进行转换。例如,可以给一个需要Shape参数的方法传入一个Rectangle参数吗?下面用实例说明这些扩展的优点。
在.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape基类。声明Display()方法是为了接受Shape类型的对象作为其参数:
public void Display(Shape o) { }
现在可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以Rectangle满足Shape的所有要求,编译器接受这个方法调用:
Rectangle r = new Rectangle() { Width = 5, Height = 2.5 };
Display(r);
方法的返回类型是抗变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来是可行的:如果一个方法向GetRectangle()方法那样返回一个Rectangle,
public Rectangle GetRectangle();
就可以把结果赋予某个Shape:
Shape s = GetRectangle();
在.NET Framework 4版本之前,这种行为方式不适用于泛型。在C#4中,扩展后的语言支持泛型接口和泛型委托的协变和抗变。下面开始定义Shape基类和Rectangle类:
public class Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override string ToString()
{
return string.Format("Width: {0}, Height: {1}.", Width.ToString(), Height.ToString());
}
}
public class Rectangle : Shape
{
}
二、泛型接口的协变
如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口IIndex与类型T是协变的,并从一个只读索引器中返回这个类型:
public interface IIndex
{
T this[int index] { get; }
int Count { get; }
}
IIndex public class RectangleCollection : IIndex
{
private readonly Rectangle[] _data = new Rectangle[3]
{
new Rectangle{Height = 2,Width = 5},
new Rectangle{Height = 3,Width = 4},
new Rectangle{Height = 4,Width = 5},
};
public static RectangleCollection GetRectangles()
{
return new RectangleCollection();
}
public Rectangle this[int index]
{
get
{
if (index < 0 || index > _data.Length)
throw new ArgumentOutOfRangeException("index");
return _data[index];
}
}
public int Count { get { return _data.Length; } }
}
RectangleCollection.GetRectangles()方法返回一个实现IIndex static void Main(string[] args)
{
//协变
IIndex rectangles = RectangleCollection.GetRectangles();
IIndex shapes = rectangles;
for (var i = 0; i < shapes.Count; i++)
{
Console.WriteLine(shapes[i]);
}
}
运行结果如下:
三、泛型接口的抗变
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入:
public interface IDisplay
{
void Show(T s);
}
ShapeDisplay类实现了IDisplay public class ShapeDisplay : IDisplay
{
public void Show(Shape s)
{
Console.WriteLine("{0} Width: {1}, Height: {2}.", s.GetType().Name, s.Width, s.Height);
}
}
创建ShapeDisplay的一个新实例,会返回IDisplay
private static void Main(string[] args)
{
//协变
IIndex rectangles = RectangleCollection.GetRectangles();
IIndex shapes = rectangles;
for (var i = 0; i < shapes.Count; i++)
{
Console.WriteLine(shapes[i]);
}
//抗变
IDisplay shapeDisplay = new ShapeDisplay();
IDisplay rectangleDisplay = shapeDisplay;
rectangleDisplay.Show(rectangles[0]);
}
运行结果如下:
本文摘自《C#高级编程 第七版》中文版(p128-p131)。