我在方差方面的经验很少,但是阅读了相当多的资料后,我相信我至少理解了基本概念(即方差描述了两种关系以及这两种关系类似地投影之间的关系)。然而,我似乎无法领会将 IComparable<T>
定义为逆变的意义或好处。乍一看,这实际上似乎会妨碍子类型之间的可比性。我希望有人能够对此进行解释。
我在方差方面的经验很少,但是阅读了相当多的资料后,我相信我至少理解了基本概念(即方差描述了两种关系以及这两种关系类似地投影之间的关系)。然而,我似乎无法领会将 IComparable<T>
定义为逆变的意义或好处。乍一看,这实际上似乎会妨碍子类型之间的可比性。我希望有人能够对此进行解释。
首先,我会处理 IComparer<T>
- 尽管这并未在您的问题中提到,但它比较容易地引导您熟悉 IComparable<T>
。
假设您有三个类:
编写一个 AreaComparer : IComparer<Shape>
是相当容易的。
协变性允许您通过面积对 List<Circle>
进行排序,因为 IComparer<Shape>
(例如 AreaComparer
)可以转换为 IComparer<Circle>
。
同样适用于 IComparable<T>
- 如果 Shape
本身使用 Area
声明自己为 IComparable<Shape>
,那么您也可以对 List<Circle>
进行排序,因为每个 Circle 都可以作为 Shape 自身进行比较。
现在很多时候这并不是问题,因为您可以从 Circle 隐式地转换为 Shape。但是,将 Circle 视为 IComparable<Circle>
的自然能力可以帮助泛型方法的类型推断。例如,假设我们有:
void Foo<T>(IComparable<T> item1, T item2)
我们尝试进行调用
Foo(circle1, circle2);
我不确定编译器是否能在没有逆变的情况下推断出T=Shape
,这样会起作用...但即使它能够,也会失败,原因如下:
void Foo<T>(IComparable<T> item1, T item2) where T : ISomethingCircleImplements
我们真正希望编译器能够满足 T=Circle
,我建议采用协变的方式,前提是 Circle
是一个通过协变成为 IComparable<Circle>
的接口。
编辑:这里有一个成功运行的例子:
using System;
public abstract class Shape : IComparable<Shape>
{
public abstract double Area { get; }
public int CompareTo(Shape other)
{
return Area.CompareTo(other.Area);
}
}
public interface ISomethingCircleImplements {}
public class Circle : Shape, ISomethingCircleImplements
{
private readonly double radius;
public Circle(double radius)
{
this.radius = radius;
}
public override double Area { get { return radius * radius * Math.PI; } }
}
class Test
{
static void Foo<T>(IComparable<T> item1, T item2)
where T : ISomethingCircleImplements
{
Console.WriteLine(item1.CompareTo(item2));
}
static void Main()
{
Circle c1 = new Circle(10);
Circle c2 = new Circle(20);
Foo<Circle>(c1, c2);
}
}
T
进行类型推断。我不确定为什么这样做不起作用,但当您指定T=Circle
时,它是可以正常工作的。 - Jon Skeet