c# 高级编程 5章109页 【泛型】【泛型接口】【协变和抗变】

127 阅读1分钟

泛型接口

  • 当接口中定义的方法需要带上泛型参数时,可以定义泛型接口
public interface IComparable<in T>
{
    int CompareTo(T other);
}
public class Person : IComparable<Person>
{
    public int CompareTo(Person other) => LastName.CompareTo(other.LastName);
}

作为对比:如果不定义泛型接口,而是用IComparable接口,则需多做一个objectT的类型转换

public class Person : IComparable
{
    public int CompareTo(object obj)
    {
        Person other = obj as Person;  //将obj转换成Person
        return this.lastname.CompareTo(other.LastName);
    }
}

协变和抗变

协变和抗变,是指对参数返回值类型进行转换 有如下前提条件: Rectangle继承了Shape

public class Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override string ToString () => $"Width: {Width}, Height: {Height}"
}
public class Rectangle : Shape
{
}

.NET中,方法的参数是抗变的,如下示例:

public void Display(Shape o) {}
var r =  new Rectangle { Width = 5, Height = 2.5 };
Display(r);  //编译器接受Rectangle类型传给Shape类型

.NET中,方法的返回值是协变的,如下示例:

public Rectangle GetRectangle();
Shape s = GetRectangle();

泛型接口,如果不加in和out,那就是不变的,而不是协变或抗变的

泛型接口的抗变

  • 加了in, 就意味着泛型接口IDisplay<in T>抗变的了
  • 加了in,接口IDisplay<in Shape>就:
    • 只能Shape当方法的输入来用
    • Shape作为方法入参的事儿,Rectangle一样能代劳
  • 因此,允许用IDisplay<Rectangle>调用只在IDisplay<Shape>定义的Show方法,这叫泛型接口IDisplay<in T>的抗变

泛型接口I接口<in T>的抗变子类类型允许传给基类作为类型的方法参数,因此,允许用I接口<子类>调用只在I接口<基类>定义的方法

所谓“抗”,“抗”的是:IX<基>变IX<子>之后,“能力”的变化。也就是说,IX<基>变IX<子>,IX<基>的“能力”,依然被IX<子>所持有

public interface IDisplay<in T>
{
    void Show(T item);
}
public class ShapeDisplay : IDisplay<Shape>
{
    public void Show(Shape s) => Console.WriteLine($"{s.GetType().Name} Width: {s.Width}, Height: {s.Height}");
}
static void Main()
{
    IDisplay<Shape> shapeDisplay = new ShapeDisplay();
    IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
    rectangleDisplay.Show(rectangles[0]);
}

泛型接口的协变

  • 加了out, 就意味着泛型接口IIndex<out T>协变的了
  • 加了out,接口IIndex<out Rectangle>就:
    • 只能Rectangle作为方法的返回类型
    • IIndex<out Rectangle>的方法的返回类型永远都是Rectangle
    • 因此,即便下一步是将IIndex<out Rectangle>赋给IIndex<out Shape>, 这个Shape其实拥有Rectangle的能力。
    • IIndex<out Shape>也可以放心调用只在IIndex<out Rectangle>定义的方法,这叫泛型接口IIndex<out T>的协变

泛型接口I接口<in T>的协变:允许用I接口<基类>调用只在I接口<子类>定义的方法

所谓“协”,“协”的是:IX<子>变IX<基>之后,“能力”的变化。也就是说,IX<子>变IX<基>,IX<子>的“能力”,居然能被IX<基>所持有

    public interface IIndex<out T>
    {
        T this[int index] { get; }
        int Count { get; }
    }
    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() => coll ?? (coll = new RectangleCollection());

        public Rectangle this[int index]
        {
            get
            {
                if (index < 0 || index > data.Length)
                    throw new ArgumentOutOfRangeException(nameof(index));
                return data[index];
            }
        }
        public int Count => data.Length;
    }
        static void Main()
        {
            IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();
            IIndex<Shape> shapes = rectangles;

            for (int i = 0; i < shapes.Count; i++)
            {
                Console.WriteLine(shapes[i]);
            }
        }