IComparable и IComparable <T> - PullRequest
       24

IComparable и IComparable <T>

34 голосов
/ 04 сентября 2011

Должен ли я реализовать как IComparable, так и общий IComparable<T>? Существуют ли ограничения, если я реализую только один из них?

Ответы [ 3 ]

21 голосов
/ 11 мая 2013

Одед прав, что вы должны реализовать оба, потому что существуют коллекции и другие классы, которые полагаются только на одну из реализаций.

Но здесь есть хитрость: IComparable не должен выдавать исключения, а IComparable - не должен. При реализации IComparable вы отвечаете за то, чтобы все экземпляры T можно было сравнивать друг с другом. Это также включает и ноль (обрабатывайте нуль как меньший, чем все ненулевые экземпляры T, и все будет в порядке).

Однако общий IComparable принимает System.Object, и вы не можете гарантировать, что все мыслимые объекты будут сопоставимы с экземплярами T. Поэтому, если вы получаете экземпляр не-T, переданный в IComparable, просто выведите исключение System.ArgumentException. В противном случае перенаправьте вызов в реализацию IComparable .

Вот пример:

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 : Как реализовать интерфейс IComparable в базовых и производных классах

21 голосов
/ 04 сентября 2011

Да, вы должны реализовать оба.

Если вы реализуете один, любой код, который зависит от другого, потерпит неудачу.

Существует много кода, который использует либо IComparable, либо IComparable<T>, но не оба, поэтому реализация обоих гарантирует, что ваш код будет работать с таким кодом.

4 голосов
/ 13 сентября 2011

Хотя IEquatable , как правило, не должен быть реализован незакрытыми классами, так как такой вывод будет играть странно с наследованием, если реализация просто не вызывает Object.Equals (в этом случае это будет бессмысленно), противоположная ситуация возникает с универсальным IComparable . Семантика для Object.Equals и IEquatable подразумевает, что всякий раз, когда IEquatable определен, его поведение должно отражать поведение Object.Equals (за исключением, возможно, более быстрого и избегания бокса). Два объекта типа DerivedFoo, которые сравниваются как равные, если рассматривать их как тип DerivedFoo, также должны сравниваться равными, если рассматривать их как объекты типа Foo, и наоборот. С другой стороны, вполне возможно, что два объекта типа DerivedFoo, которые имеют неравный ранг, если рассматривать их как тип DerivedFoo, должны иметь одинаковый ранг, если рассматривать их как тип Foo. Единственный способ убедиться в этом - использовать IComparable .

Предположим, например, что есть класс SchedulerEvent, который содержит поля ScheduledTime (типа DateTime) и ScheduledAction (типа MethodInvoker). Класс включает в себя подтипы SchedulerEventWithMessage (который добавляет поле Message типа string) и SchedulerEventWithGong (который добавляет поле GongVolume типа Double). Класс SchedulerEvent имеет естественное упорядочение по ScheduledTime, но вполне возможно, что события, которые неупорядочены относительно друг друга, будут неравными. Классы SchedulerEventWithMessage и SchedulerEventWithGong также имеют естественные упорядочения между собой, но не по сравнению с элементами класса SchedulerEvent.

Предположим, что у одного есть два события SchedulerEventWithMessage X и Y, запланированные на одно и то же время, но X.Message - "aardvark", а Y.Message - "zymurgy". ((IComparable ) X) .CompareTo (Y) должен сообщать ноль (поскольку события имеют равное время), но ((IComparable ) X) .CompareTo (Y) должен возвращать отрицательное число (так как «aardvark» роды перед "зимургой"). Если бы класс не вел себя таким образом, было бы трудно или невозможно последовательно упорядочить список, который содержит смесь объектов SchedulerEventWithMessage и SchedulerEventWithGong.

Кстати, можно утверждать, что было бы полезно, чтобы семантика IEquatable сравнивала объекты только на основе членов типа T, так что, например, IEquatable будет проверять ScheduledTime и ScheduledAction на равенство, но даже при применении к SchedulerEventWithMessage или SchedulerEventWithGong не будет проверять свойства Message или GongVolume. Действительно, это было бы полезно для семантики IEquatable , и я предпочел бы такую ​​семантику, но для одной проблемы: Comparer .Default.GetHashCode (T) всегда вызывает одну и ту же функцию Object.GetHashCode () независимо от тип T. Это значительно ограничивает способность IEquatable изменять свое поведение в зависимости от типа T.

...