IComparable<T>作为逆变的好处是什么?

4

我在方差方面的经验很少,但是阅读了相当多的资料后,我相信我至少理解了基本概念(即方差描述了两种关系以及这两种关系类似地投影之间的关系)。然而,我似乎无法领会将 IComparable<T> 定义为逆变的意义或好处。乍一看,这实际上似乎会妨碍子类型之间的可比性。我希望有人能够对此进行解释。

1个回答

7

首先,我会处理 IComparer<T> - 尽管这并未在您的问题中提到,但它比较容易地引导您熟悉 IComparable<T>

假设您有三个类:

  • Shape(具有 Area 属性)
  • Circle:Shape
  • Square:Shape

编写一个 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);
    }
}

有趣的是,类型推断在这里不起作用 - 但我不确定为什么。然而,逆变本身是可以的。

@blf:可能值得提出一个关于这个问题的单独问题,并附上完整的代码。 - Jon Skeet
Jon,我一定会的! :) - blf
Jon,我真的很不想打扰你,但有趣的是,除非circle实现自己的IComparable<Circle>,否则这个程序无法编译。如果您想看到测试用例,我可以发布它。这是使用编译器版本4.0.30319.1编译的。 - blf
@blf:与其继续这个非常长的评论线程,我建议你把它放到一个新问题中。 - Jon Skeet
@blf:我已经编辑了我的代码以展示它的工作原理,尽管没有为 T 进行类型推断。我不确定为什么这样做不起作用,但当您指定 T=Circle 时,它是可以正常工作的。 - Jon Skeet
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接