1. 用 in 和 out 表示可变性
我们使用的两个接口是 IEnumerable<T> (对于 T 是协变的)和 IComparer<T> (对于 T 是逆变的),它们可以很好地展示可变性。以下是它们在.NET 4中的声明:
public interface IEnumerable<out T> public interface IComparer<in T>
这非常好记:如果类型参数只用于输出,就使用 out ;如果只用于输入,就用 in 。编译器可不知道你是否记住了哪种形式是协变,哪种形式是逆变。
1 public interface IShape 2 { 3 double Area { get; } 4 5 System.Windows.Rect BoundingBox { get; } 6 } 7 /// <summary> 8 /// WindowsBase.dll下 9 /// </summary> 10 public sealed class Circle : IShape 11 { 12 private readonly System.Windows.Point center; 13 public System.Windows.Point Center { get { return center; } } 14 15 private readonly double radius; 16 public double Radius { get { return radius; } } 17 18 public Circle(System.Windows.Point center, int radius) 19 { 20 this.center = center; 21 this.radius = radius; 22 } 23 24 public double Area 25 { 26 get { return Math.PI * radius * radius; } 27 } 28 29 public System.Windows.Rect BoundingBox 30 { 31 get 32 { 33 return new System.Windows.Rect(center - new System.Windows.Vector(radius, radius), new System.Windows.Size(radius * 2, radius * 2)); 34 } 35 } 36 } 37 public sealed class Square : IShape 38 { 39 private readonly Point topLeft; 40 private readonly double sideLength; 41 42 public Square(Point topLeft, double sideLength) 43 { 44 this.topLeft = topLeft; 45 this.sideLength = sideLength; 46 } 47 48 public double Area { get { return sideLength * sideLength; } } 49 50 public Rect BoundingBox 51 { 52 get { return new Rect(topLeft, new Size(sideLength, sideLength)); } 53 } 54 }
2. 接口的协变性
1 List<Circle> circles = new List<Circle>() 2 { 3 new Circle(new Point(0, 0), 15), 4 new Circle(new Point(10, 5), 20), 5 }; 6 List<Square> squares = new List<Square>() 7 { 8 new Square(new Point(5, 10), 5), 9 new Square(new Point(-10, 0), 2), 10 }; 11 12 List<IShape> shapesByAdding = new List<IShape>(); 13 shapesByAdding.AddRange(circles); 14 shapesByAdding.AddRange(squares); 15 List<IShape> shapesByConcat = circles.Concat<IShape>(squares).ToList();
实际上,代码清单13-12在4个地方使用了协变性,对于类型系统而言,每次将圆形或方形序列转换为普通几何形状时,都用到了协变性。
首先新建了一个 List<IShape> ,并调用 AddRange向其添加圆形和方形列表 。(我们也可以向构造函数传递一个列表,然后调用 AddRange 一次。)
List<T>.AddRange 的参数为 IEnumerable<T> 类型,因此在这种情况下我们将这两个列表都看成是 IEnumerable<IShape> ,而这在以前是不被允许的。
3. 接口的逆变性
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 List<Circle> circles = new List<Circle>() 6 { 7 new Circle(new Point(0, 0), 15), 8 new Circle(new Point(10, 5), 20), 9 }; 10 List<Square> squares = new List<Square>() 11 { 12 new Square(new Point(5, 10), 5), 13 new Square(new Point(-10, 0), 2), 14 }; 15 16 IComparer<IShape> areaComparer = new AreaComparer(); 17 circles.Sort(areaComparer); 18 } 19 } 20 class AreaComparer : IComparer<IShape> 21 { 22 public int Compare(IShape x, IShape y) 23 { 24 return x.Area.CompareTo(y.Area); 25 } 26 }