IComparable 和 IComparable
我应该同时实现 IComparable
和通用 IComparable
吗?如果我只实现其中之一,有什么限制吗?
Should I implement both IComparable
and the generic IComparable<T>
? Are there any limitations if I only implement one of them?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
是的,您应该两者都实施。
如果您实现其中一个,则任何依赖于另一个的代码都将失败。
有很多代码使用
IComparable
或IComparable
但不能同时使用两者,因此同时实现这两种代码可确保您的代码能够与此类代码配合使用。Yes, you should implement both.
If you implement one, any code that depends on the other will fail.
There is lots of code that uses either
IComparable
orIComparable<T>
but not both, so implementing both ensure your code will work with such code.Oded 是对的,您应该同时实现这两种实现,因为有些集合和其他类仅依赖于其中一种实现。
但这里有一个技巧:IComparable不应该抛出异常,而 IComparable 应该抛出异常。当实现 IComparable时您负责确保 T 的所有实例都可以相互比较。这也包括 null(将 null 视为小于 T 的所有非空实例,这样就可以了)。
但是,一般 IComparable 接受 System.Object,并且您不能保证所有可以想象的对象都可以与 T 的实例进行比较。因此,如果您将非 T 实例传递给 IComparable,只需抛出 System.ArgumentException。否则,将呼叫路由至 IComparable接口。执行。
下面是示例:
此示例是一篇较长文章的一部分,其中包含对实现 IComparable时应注意的副作用的广泛分析: 如何实现 IComparable基类和派生类中的接口
Oded is right that you should implement both because there are collections and other classes that rely on only one of the implementations.
But there is a trick there: IComparable<T> should not throw exceptions, while IComparable should. When implementing IComparable<T> you are in charge to ensure that all instances of T can be compared against each others. This includes null, as well (treat null as smaller than all non-null instances of T and you'll be fine).
However, general IComparable accepts System.Object and you can't guarantee that all conceivable objects would be comparable against instances of T. Therefore, if you get a non-T instance passed to IComparable, simply throw the System.ArgumentException. Otherwise, route the call to the IComparable<T> implementation.
Here is the example:
This example is part of a much longer article which contains extensive analysis of side-effects that you should take care of when implementing IComparable<T>: How to Implement IComparable<T> Interface in Base and Derived Classes
而IEquatable通常不应由未密封的类实现,因为除非实现简单地调用 Object.Equals(在这种情况下它将毫无意义),否则这种派生会与继承产生奇怪的关系,而通用 IComparable会出现相反的情况。 Object.Equals 和 IEquatable的语义暗示每当 IEquatable定义后,它的行为应该反映 Object.Equals 的行为(除了可能更快并避免装箱)。当被视为 DerivedFoo 类型时比较相等的两个 DerivedFoo 类型对象在被视为 Foo 类型对象时也应该比较相等,反之亦然。另一方面,两个 DerivedFoo 类型的对象在被视为 DerivedFoo 类型时排名不等,而在被视为 Foo 类型时完全有可能排名相等。确保这一点的唯一方法是使用 IComparable。
例如,假设有一个类 SchedulerEvent,其中包含字段 ScheduledTime(类型为 DateTime)和 ScheduledAction(类型为 MethodInvoker)。该类包括子类型SchedulerEventWithMessage(添加字符串类型的Message 字段)和SchedulerEventWithGong(添加Double 类型的GongVolume 字段)。 SchedulerEvent 类具有按 ScheduledTime 进行的自然排序,但相对于彼此无序的事件完全有可能不相等。 SchedulerEventWithMessage 和 SchedulerEventWithGong 类之间也具有自然顺序,但与 SchedulerEvent 类的项目相比则不然。
假设同时安排了两个 SchedulerEventWithMessage 事件 X 和 Y,但 X.Message 是“aardvark”,Y.Message 是“zymurgy”。 ((IComparable)X).CompareTo(Y) 应报告零(因为事件具有相同的时间),但 ((IComparable)X).CompareTo(Y) 应返回负数(因为“aardvark”)排序在“zymurgy”之前)。如果该类不这样做,则很难或不可能对包含 SchedulerEventWithMessage 和 SchedulerEventWithGong 对象混合的列表进行一致排序。
顺便说一句,有人可能会争辩说,拥有 IEquatable的语义会很有用。仅比较类型 T 的成员的基础对象,以便例如 IEquatable会检查 ScheduledTime 和 ScheduledAction 是否相等,但即使应用于 SchedulerEventWithMessage 或 SchedulerEventWithGong 也不会检查 Message 或 GangVolume 属性。事实上,这些对于 IEquatable来说将是有用的语义。方法,并且我喜欢这样的语义,但有一个问题:Comparer.Default.GetHashCode(T) 总是调用相同的函数 Object.GetHashCode(),无论 T 类型如何。这极大地限制了 IEquatable的能力。改变不同类型 T 的行为。
While IEquatable<T> should generally not be implemented by unsealed classes, since such derivation would play oddly with inheritance unless the implementation simply calls Object.Equals (in which case it would be pointless), the opposite situation arises with the generic IComparable<T>. The semantics for Object.Equals and IEquatable<T> imply that whenever IEquatable<T> is defined, its behavior should mirror that of Object.Equals (aside from possibly being faster and avoiding boxing). Two objects of type DerivedFoo that compare as equal when regarded as type DerivedFoo should also compare equal when regarded as objects of type Foo, and vice versa. On the other hand, it is entirely possible that two objects of type DerivedFoo that rank unequally when regarded as type DerivedFoo should rank equally when regarded as type Foo. The only way to assure this is to use IComparable<T>.
Suppose, for example, one has a class SchedulerEvent which contains fields ScheduledTime (of type DateTime) and ScheduledAction (of type MethodInvoker). The class includes subtypes SchedulerEventWithMessage (which adds a Message field of type string) and SchedulerEventWithGong (which adds a GongVolume field of type Double). The SchedulerEvent class has a natural ordering, by ScheduledTime, but it's entirely possible for events which are unordered relative to each other to be unequal. The SchedulerEventWithMessage and SchedulerEventWithGong classes also have natural orderings among themselves, but not when compared to items of class SchedulerEvent.
Suppose one has two SchedulerEventWithMessage events X and Y scheduled for the same time, but X.Message is "aardvark" and Y.Message is "zymurgy". ((IComparable<SchedulerEvent>)X).CompareTo(Y) should report zero (since the events have equal times) but ((IComparable<SchedulerEventWithMessage>)X).CompareTo(Y) should return a negative number (since "aardvark" sorts before "zymurgy"). If the class did not behave that way, it would be difficult or impossible to consistently order a list which contains a mixture of SchedulerEventWithMessage and SchedulerEventWithGong objects.
Incidentally, one might argue that it would be useful to have the semantics of IEquatable<T> compare objects only the basis of the members of type T, so that e.g. IEquatable<SchedulerEvent> would check ScheduledTime and ScheduledAction for equality, but even when applied to a SchedulerEventWithMessage or SchedulerEventWithGong would not check Message or GongVolume properties. Indeed, those would be useful semantics for an IEquatable<T> method, and I would favor such semantics, but for one problem: Comparer<T>.Default.GetHashCode(T) always calls the same function Object.GetHashCode() regardless of type T. This greatly limits the ability of IEquatable<T> to vary its behavior with different types T.