1. 程式人生 > >泛型接口(協變和逆變)

泛型接口(協變和逆變)

支持 逆變 tor 委托 length data display null static

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

泛型接口(協變和逆變)