泛型接口(協變和逆變)
使用泛型可以定義接口,在接口中定義的方法可以帶泛型參數。在鏈表的中,實現了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]); }
泛型接口(協變和逆變)