[Professional C# 7] [Covariance and Contra-Variance]【协变/逆变】

Prior to .NET 4, generic interfaces were invariant. .NET 4 added important changes for generic interfaces and generic delegates: covariance and contra-variance.

Covariance and contra-variance are used for the conversion of types with arguments and return types.

With .NET, parameter types are covariant. Assume you have the classes Shape and Rectangle, and
Rectangle derives from the Shape base class. The Display method is declared to accept an object of the Shape type as its parameter:

public void Display(Shape o) { }

Now you can pass any object that derives from the Shape base class. Because Rectangle derives from
Shape, a Rectangle fulfills all the requirements of a Shape and the compiler accepts this method call:

var r = new Rectangle { Width= 5, Height=2.5 };
Display(r);

Return types of methods are contra-variant. When a method returns a Shape it is not possible to assign it
to a Rectangle because a Shape is not necessarily always a Rectangle; but the opposite is possible. If a
method returns a Rectangle as the GetRectangle method

public Rectangle GetRectangle();

the result can be assigned to a Shape:

Shape s = GetRectangle();

 Covariance with Generic Interfaces

A generic interface is covariant if the generic type is annotated with the out keyword.

This also means that type T is allowed only with return types.

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

The RectangleCollection.GetRectangles method returns a RectangleCollection that implements
the IIndex<Rectangle> interface, so you can assign the return value to a variable rectangle of the
IIndex<Rectangle> type. Because the interface is covariant, it is also possible to assign the returned value to a variable of IIndex<Shape>.

public static void Main()
{
  IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();
  IIndex<Shape> shapes = rectangles;
  for (int i = 0; i < shapes.Count; i++)
  {
    Console.WriteLine(shapes[i]);
  }
}

 Contra-Variance with Generic Interfaces

A generic interface is contra-variant if the generic type is annotated with the in keyword.
This way, the interface is only allowed to use generic type T as input to its method

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

Creating a new instance of ShapeDisplay returns IDisplay<Shape>, which is assigned to the shapeDisplay variable.
Because IDisplay<T> is contra-variant, it is possible to assign the result to
IDisplay<Rectangle>, where Rectangle derives from Shape. This time the methods of the interface
define only the generic type as input, and Rectangle fulfills all the requirements of a Shape

public static void Main()
{
  //...
  IDisplay<Shape> shapeDisplay = new ShapeDisplay();
  IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
  rectangleDisplay.Show(rectangles[0]);
}

 

posted @ 2022-08-02 15:39  FH1004322  阅读(34)  评论(0)    收藏  举报