泛型接口(协变和逆变)

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]);   
}

 

以上是关于泛型接口(协变和逆变)的主要内容,如果未能解决你的问题,请参考以下文章

Typescript 中的协变和逆变

协变和逆变

Java泛型中的协变和逆变

scala中泛型,协变和逆变

c#泛型的协变和逆变

为啥 C# (4.0) 不允许泛型类类型中的协变和逆变?