C# IComparable的好处<;T>;作为逆变?

C# IComparable的好处<;T>;作为逆变?,c#,contravariance,icomparable,C#,Contravariance,Icomparable,我对方差几乎没有经验,但在阅读了相当多的内容后,我相信我至少理解了基本概念(即方差描述了两种类型之间的关系以及这两种类型之间的关系)。然而,我似乎无法理解将IComparable定义为逆变的意义或好处。乍一看,这实际上似乎妨碍了子类型之间的可比性。我希望有人能对这件事有所了解。我将首先解决IComparer——你的问题中没有提到,但这是一种更容易的“销售”,然后会导致IComparable 假设您有三个类: 形状(具有面积特性) 圆形:形状 正方形:形状 编写区域比较器:IComparer很

我对方差几乎没有经验,但在阅读了相当多的内容后,我相信我至少理解了基本概念(即方差描述了两种类型之间的关系以及这两种类型之间的关系)。然而,我似乎无法理解将
IComparable
定义为逆变的意义或好处。乍一看,这实际上似乎妨碍了子类型之间的可比性。我希望有人能对这件事有所了解。

我将首先解决
IComparer
——你的问题中没有提到,但这是一种更容易的“销售”,然后会导致
IComparable

假设您有三个类:

  • 形状(具有面积特性)
  • 圆形:形状
  • 正方形:形状
编写
区域比较器:IComparer
很容易

相反允许您按区域对
列表
进行排序,因为
i比较程序
(例如
AreaComparer
)可转换为
i比较程序

同样,对于
i可比较
,如果
形状
本身使用
区域
声明自己是
i可比较的
,那么您可以再次对
列表
进行排序,因为每个圆都可以作为一个形状与自身相比较

现在很多时候,这实际上不是问题,因为你会有一个从
形状
的隐式转换。但是
被视为
i可比较
的自然能力在泛型方法的类型推断方面可能有所帮助。例如,假设我们有:

void Foo<T>(IComparable<T> item1, T item2)
我不知道编译器是否能够(不使用逆变)推断出
t=Shape
,这会起作用。。。但即使可以,它也会因为以下原因而失败:

void Foo<T>(IComparable<T> item1, T item2) where T : ISomethingCircleImplements

有趣的是,类型推断在这里不起作用——但我不知道为什么。不过,这种反差本身也不错。

Jon,现在已经多次阅读了您的回答,我认为这实际上已经开始有意义了。谢谢乔恩,看来我过早地接受了你的回答。我仍然发现自己有很多问题,我花了一些时间实现了一个涉及建议的Foo示例的测试用例。它编译时没有出现错误,“类型'Testable.Shape'不能用作泛型类型或方法'Testable.Test.Foo(System.IComparable,t')中的类型参数't'。没有从'Testable.Shape'到'Testable.ISomethingCircleImplements'的隐式引用转换@blf:对,但您不想在那里使用t=Shape。您需要T=Circle,它只有在Circle实现了
IComparable
时才有效,而这只能通过逆变实现。Jon,我明白了。我误解了。我将不得不再考虑一下,但直到今天下午晚些时候才有时间运行更多的测试。也许我只是又一次误解,但我有一个想法,这可能也行不通。考虑一个实现IsHAPE和IComparer的类形状,也考虑一个包含形状的列表。然后调用List.Sort()会导致调用IComparer而不是IComparer(这似乎很不幸)。不过,感谢您的反馈!非常感谢。乔恩,好吧,我有比我想象的更多的空闲时间,所以我试了试。事实证明,正如您所建议的,当给定Foo方法时,编译器能够确定T=Circle。我觉得很奇怪,它不能对列表执行相同的操作。我要把这个标记为已接受。但是,我想知道您是否知道为什么Sort()不能做出同样的推论?再次感谢你的帮助!
void Foo<T>(IComparable<T> item1, T item2) where T : ISomethingCircleImplements
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);
    }
}