协变与抗变指对参数和返回值的类型进行转换。
在.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape基类。声明Display()方法是为了接收Shape类型的对象作为其参数。
public void Display(Shape o)
{
}
现在可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以Rectangle满足Shape的所有要求,编译器接收这个方法的调用。
var r = new Rectangle{Width = 5, Height = 2.5};
Display(r);
方法的返回类型是抗变的。当方法返回一个Shape是,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来是可行的:如果一个方法像GEtRectangle()方法那样返回一个Rectangle。
public Rectangle GetRectangle()
{
}
// 调用
Shape s = GetRectangle();
如果泛型接口类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口IIndex与类型T是协变的,并从一个只读索引器中返回这个类型。
public interface IIndex
{
T this[int index] { get; }
int Count { get; }
}
IIndex
public class RectangleCollection : IIndex
{
private Rectangle[] data = new Rectangle[3]
{
new Rectangle { Height=2, Width=5 },
new Rectangle { Height=3, Width=7},
new Rectangle { Height=4.5, Width=2.9}
};
private static RectangleCollection coll;
public static RectangleCollection GetRectangles() => coll ?? (coll = new RectangleCollection());
public Rectangle this[int index]
{
get
{
if (index < 0 || index > data.Length)
throw new ArgumentOutOfRangeException("index");
return data[index];
}
}
public int Count => data.Length;
}
注意:如果对接口IIndex使用了读写所引器,就把泛型类型T传递给方法,并从方法中检索这个类型。这不能通过协变来实现——泛型乐星必须定义为不变的。不能用out和in标注,就可以把类型定义为不变的。
RectangleCollection.GetRectangle()方法返回一个实现IIndex
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入。
public interface IDisplay
{
void Show(T item);
}
ShapeDisplay类实现IDisplay
public class ShapeDisplay : IDisplay
{
public void Show(Shape s) => WriteLine($"{s.GetType().Name} Width: {s.Width}, Height: {s.Height}");
}
创建ShapeDisplay的一个新实例,会返回IDisplay