IComparable and IComparable<T>

42

我应该同时实现IComparable和通用的IComparable<T>吗?如果我只实现其中一个是否有任何限制?


你只需要编写 IComparable<T> 的代码。通过将实际比较委托给通用实现,你可以免费获得 IComparable - Jeff Mercado
3个回答

25

是的,你需要同时实现两者。

如果只实现其中一个,那些依赖于另一个接口的代码将会失效。

很多代码仅使用 IComparableIComparable<T> 其中之一,而实现两者可以确保你的代码能够与这些代码正常工作。


5
“我Comparable” 接口在某些框架代码中被使用。我相信 Array.SortArrayList.Sort 使用了它。 - Oded
4
嗯,太令人失望了。如果能够忘记非泛型 .net 的存在就好了。 - David Heffernan
2
是我还是第一句和最后一句有点矛盾? - H H
2
@Henk 我看不出矛盾之处。 - David Heffernan
2
我也看不到任何矛盾。最后一句话重申了只有两者都实现才能避免代码出错。 - Woot4Moo
显示剩余7条评论

24

Oded是正确的,你应该同时实现两个接口,因为有一些集合和其他类只依赖其中一个实现。

但是这里有一个诀窍:IComparable<T>不应该抛出异常,而IComparable应该。当实现IComparable<T>时,你需要确保所有T的实例都可以相互比较。这包括null(将null视为小于所有非null的T实例即可)。

然而,通用的IComparable接受System.Object类型,你无法保证所有可能的对象都可以与T的实例进行比较。因此,如果传递给IComparable的是非T实例,只需抛出System.ArgumentException异常。否则,将调用路由到IComparable<T>实现。

以下是示例:

public class Piano : IComparable<Piano>, IComparable
{
    public int CompareTo(Piano other) { ... }
    ...
    public int CompareTo(object obj)
    {

        if (obj != null && !(obj is Piano))
            throw new ArgumentException("Object must be of type Piano.");

        return CompareTo(obj as Piano);

    }
}

这个例子是一个更长文章的一部分,该文章包含了大量关于实现IComparable<T>时需要注意的副作用的分析:如何在基类和派生类中实现IComparable<T>接口


4
尽管不应该由未密封的类实现IEquatable<T>,因为这样的派生会与继承产生奇怪的影响,除非实现只是调用Object.Equals(在这种情况下它将是无意义的),但泛型IComparable<T>却出现了相反的情况。Object.Equals和IEquatable<T>的语义意味着,每当定义IEquatable<T>时,其行为都应该反映Object.Equals的行为(除了可能更快且避免装箱)。类型为DerivedFoo的两个对象,在被视为DerivedFoo类型时比较相等,而在被视为Foo类型的对象中也应该相等,反之亦然。另一方面,当将类型为DerivedFoo的两个对象视为DerivedFoo类型时,如果它们排名不相等,则完全有可能将它们视为Foo类型时排名相等。确保这一点的唯一方法是使用IComparable<T>。
例如,假设有一个包含ScheduledTime字段(类型为DateTime)和ScheduledAction字段(类型为MethodInvoker)的SchedulerEvent类。该类包括子类型SchedulerEventWithMessage(添加了一个类型为string的Message字段)和SchedulerEventWithGong(添加了一个类型为Double的GongVolume字段)。SchedulerEvent类具有自然排序,按ScheduledTime排序,但是对于彼此无序的事件来说,它们完全可能是不相等的。SchedulerEventWithMessage和SchedulerEventWithGong类在它们自己之间也有自然排序,但与SchedulerEvent类的项目进行比较时没有排序。
假设有两个ScheduledTime相同的SchedulerEventWithMessage事件X和Y,但X.Message是"aardvark",而Y.Message是"zymurgy"。((IComparable<SchedulerEvent>)X).CompareTo(Y)应该报告零(因为事件具有相等的时间),但((IComparable<SchedulerEventWithMessage>)X).CompareTo(Y)应该返回负数(因为"aardvark"在"zymurgy"之前)。如果该类不是这样工作的,那么很难或者不可能一致地对包含SchedulerEventWithMessage和SchedulerEventWithGong对象的列表进行排序。
顺便说一句,人们可能会认为,将IEquatable<T>的语义仅基于类型为T的成员进行比较是有用的,以便例如IEquatable<SchedulerEvent>将检查ScheduledTime和ScheduledAction是否相等,但即使应用于SchedulerEventWithMessage或SchedulerEventWithGong,也不会检查Message或GongVolume属性。实际上,这些对于IEquatable<T>方法来说将是有用的语义,我会支持这种语义,但有一个问题:Comparer<T>.Default.GetHashCode(T)始终调用相同的Object.GetHashCode()函数,而不考虑类型T。这极大地限制了IEquatable<T>在不同类型T下变化其行为的能力。

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