IComparable and IComparable

2019-02-02 06:52发布

Should I implement both IComparable and the generic IComparable<T>? Are there any limitations if I only implement one of them?

3条回答
欢心
2楼-- · 2019-02-02 07:28

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 or IComparable<T> but not both, so implementing both ensure your code will work with such code.

查看更多
萌系小妹纸
3楼-- · 2019-02-02 07:35

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:

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);

    }
}

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

查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-02-02 07:37

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.

查看更多
登录 后发表回答