Должен ли я реализовать как IComparable
, так и общий IComparable<T>
? Существуют ли какие-либо ограничения, если я реализую только один из них?
IComparable и IComparable <T>
Ответ 1
Да, вы должны реализовать оба.
Если вы его реализуете, любой код, который зависит от другого, не будет работать.
Существует много кода, который использует либо IComparable
, либо IComparable<T>
, но не для обоих, поэтому реализация обоих гарантирует, что ваш код будет работать с таким кодом.
Ответ 2
Oded прав, что вы должны реализовать оба, потому что есть коллекции и другие классы, которые полагаются только на одну из реализаций.
Но есть трюк: IComparable <T> не следует бросать исключения, в то время как IComparable должен. При реализации IComparable <T> вы отвечаете за то, чтобы все экземпляры T могли сравниваться друг с другом. Это также включает в себя null (обрабатывать значение null как меньше, чем все ненулевые экземпляры T, и все будет хорошо).
Однако общий IComparable принимает System.Object, и вы не можете гарантировать, что все мыслимые объекты будут сопоставимы с экземплярами T. Поэтому, если вы получите экземпляр non-T, переданный в IComparable, просто выбросите 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> Интерфейс в базовых и производных классах
Ответ 3
В то время как IEquatable <T> как правило, не реализуются незапечатанными классами, поскольку такой вывод будет играть странно с наследованием, если только реализация просто не вызовет Object.Equals(в этом случае это будет бессмысленно), возникает противоположная ситуация с общим IComparable <T> . Семантика для Object.Equals и IEquatable <T> подразумевают, что всякий раз, когда IEquatable <T> определяется, его поведение должно отражать поведение Object.Equals(кроме, возможно, более быстрого и избегающего бокса). Два объекта типа DerivedFoo, которые сравниваются как равные, если их рассматривать как тип DerivedFoo, также должны сравнивать равные, если рассматривать их как объекты типа Foo, и наоборот. С другой стороны, вполне возможно, что два объекта типа DerivedFoo, которые ранжируются неравномерно, если их рассматривать как тип DerivedFoo, должны одинаково оцениваться, если рассматривать их как тип Foo. Единственный способ убедиться в этом - использовать IComparable <T> .
Предположим, например, что у вас есть класс SchedulerEvent, который содержит поля ScheduledTime (типа DateTime) и ScheduledAction (типа MethodInvoker). Класс включает подтипы SchedulerEventWithMessage (который добавляет поле Message строки типа) и SchedulerEventWithGong (который добавляет поле GongVolume типа Double). Класс SchedulerEvent имеет естественное упорядочение по ScheduledTime, но вполне возможно, что события, которые неупорядочены относительно друг друга, неравны. Классы SchedulerEventWithMessage и SchedulerEventWithGong также имеют естественный порядок между собой, но не по сравнению с элементами класса SchedulerEvent.
Предположим, что для одного и того же времени запланировано два события SchedulerEventWithMessage X и Y, но X.Message - это "aardvark" и Y.Message - "zymurgy". ((IComparable <SchedulerEvent> ) X).CompareTo(Y) должен сообщать о нуле (поскольку события имеют одинаковое время), но ((IComparable <SchedulerEventWithMessage> ) X).CompareTo(Y) должен возвращать отрицательное число (поскольку "aardvark" до "зимургии" ). Если класс не вел себя так, было бы сложно или невозможно последовательно упорядочить список, содержащий смесь объектов 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.