泛型接口(协变和逆变)
Posted pilgrim
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了泛型接口(协变和逆变)相关的知识,希望对你有一定的参考价值。
使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。在链表的中,实现了IEnumerable<out T>接口,它定义了GetEnumerator()方法,返回IEnumerator<T>。.net中提供了许多泛型接口:IComparable<T>、ICollection<T>和IextensibleObject<T>等。同一个接口一般存在一个比较老的非泛型版本接口存在,如IComparable:
在非泛型版本中,其参数是object类型(在.net中,object是一切类型的基础)。因此,在实现比较方法前,需要经过类型转换,才能使用:
//非泛型版本比较接口 public interface IComparable { int CompareTo(object obj); } // 非泛型实现比较接口 class Person : IComparable { public int CompareTo(object obj) { Person other = obj is Person ? obj as Person : null;//比较前,必须要强制转换类型 throw new NotImplementedException(); } }
在泛型版本中,则不需要经过转换类型,其实现方法时,会自动将当前类型作为参数的类型输入。
public interface IComparable<in T> { int CompareTo(T other); } //泛型实现比较接口 class Person : IComparable<Person> { public int CompareTo(Person other) { throw new NotImplementedException(); } }
1、协变与逆变
在.net 4.0之前,泛型接口时不变的。.net 4通过协变和逆变为泛型接口和泛型委托添加了一个重要的扩展。协变和逆变是指对参数和返回值的类型进行转换。
在.net中,参数类型是协变的。如有一个Shape类和Rectangle类,其中Shape是Rectangle类的父类。声明Display()方法是为了接受Shape类型的对象作为参数:public void Show(Shape s){ }
Display()方法可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以下面的代码合法:
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, Height); } } public class Rectangle : Shape { } Rectangle r = new Rectangle { Width = 5, Height = 10 }; Display(r);
方法的返回类型是逆变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定是Rectangle的。但是,反过来是可行的。在.net 4之前,这种行为方式不支持泛型。之后的版本支持泛型接口和泛型委托的协变和逆变。
2、泛型接口的协变
泛型接口的协变,需要使用out关键字标注。同时也意味着返回类型只能是T。接口IIndex与类型T是协变的,并从只读索引器中返回这个类型:
public interface IIndex<out T> { T this[int index] { get; } int Count { get; } }
使用RectangleCollection类实现接口IIndex<T>:
public class RectangleCollection : IIndex<Rectangle> { 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() { return 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 { get { return data.Length; } } }
方法GetReactangles()返回一个实现IIndex<Rectangle>接口的RectangleCollection类,因此可以把返回值赋予IIndex<Rectangle>类型的变量。因为接口是协变的,所以也可以把返回值赋值给IIndex<Shape>类型的变量。(因为没有这个IIndex<Shape>实现类型的类,但是Rectangele的父类是Shape)。
static void Main() { IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles(); IIndex<Shape> shapes = rectangles; for (int i = 0; i < shapes.Count; i++) { Console.WriteLine(shapes[i]); } }
3、泛型接口的逆变
泛型接口的逆变需要关键字in实现。这样,接口只能把泛型类型T用作其方法的输入:
public interface IDisplay<in T> { void Show(T item); }
ShapeDisplay类实现IDisplay<Shape>,并使用Shape对象作为输入参数:
public class ShapeDisplay : IDisplay<Shape> { public void Show(Shape s) { Console.WriteLine("{0} Width: {1}, Height: {2}", s.GetType().Name, s.Width, s.Height); } }
创建ShapeDisplay的一个实例,并赋于IDisplay<Shape>类型的变量。因为IDisplay<T>是逆变的,所以可以把结果赋予IDisplay<Rectangle>,Rectangle派生自Shape。接口的方法只能把泛型类型定义为输入:
static void Main() { IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles(); IDisplay<Shape> shapeDisplay = new ShapeDisplay(); IDisplay<Rectangle> rectangleDisplay = shapeDisplay; rectangleDisplay.Show(rectangles[0]); }
以上是关于泛型接口(协变和逆变)的主要内容,如果未能解决你的问题,请参考以下文章