泛型接口(协变和逆变)

  使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。在链表的中,实现了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 Display(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的。但是,反过来是可行的。

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

 

posted @   一只独行的猿  阅读(1561)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示