Почему я должен перегружать операторов при реализации CompareTo?
предположим, у меня есть тип, который реализует IComparable.
Я бы подумал, что разумно ожидать, что операторы ==
, !=
, >
, <
, >=
и <=
будет" просто работать " автоматически, вызывая CompareTo, но вместо этого я должен переопределить их все, если я хочу их использовать.
С точки зрения языкового дизайна есть ли веская причина, по которой это было сделано таким образом? Есть ли случаи, когда вы действительно полезны для A>B
вести себя иначе Compare(A,B)>0
?
2 ответов
вся ситуация досадная. C# имеет слишком много способов выразить равенство и неравенство:
- the == != > =
- статический метод Equals (который вызывает виртуальный метод), виртуальный метод Equals, метод ReferenceEquals
- icomparable и IEquatable интерфейсы
все они имеют тонко различную семантику и, за исключением статических равных, нет автоматически использует другой, и ни один на самом деле не имеет поведения, которое я хочу. Статические методы отправляются на основе типа времени компиляции обоих операндов; виртуальные методы / методы интерфейса отправляются на основе типа времени выполнения один операндов, что делает операцию асимметричной; тип одной стороны имеет большее значение, чем тип другой.
Я не могу себе представить, что кто-то думает, что ситуация, в которой мы находимся, велика; учитывая отсутствие ограничений, это это не то, что могло бы развиться. Но у конструкторов управляемых языков есть ограничения: среда CLR не реализует статические методы в контрактах интерфейса или двойной виртуальной отправке, или возможность поставить ограничение оператора на параметр универсального типа. И поэтому для решения проблемы равенства/неравенства появилось множество решений.
Я думаю, что дизайнеры CLR и C# должны были вернуться назад во времени и рассказать своим прошлым "я", какие функции должны быть в v1 CLR, в какой-то форме статических методов в интерфейсах было бы высоко в списке. Если в интерфейсе были статические методы, то мы можем определить:
interface IComparable<in T, in U>
{
static bool operator <(T t, U u);
static bool operator >(T t, U u);
... etc
и потом, если у вас есть:
static void Sort<T>(T[] array) where T : IComparable<T, T>
затем вы можете использовать <
и ==
и так далее операторы для сравнения элементов.
две основные причины:
- Это общая структура для всех операторов. Хотя операторы сравнения могут никогда не иметь альтернативной семантики, существует большая полезность в структуре, которая допускает очень различную семантику для некоторых других операторов. Реализация отдельной структуры только для операторов сравнения потребовала бы исключения какой-либо другой, возможно, гораздо более полезной функции. Проверьте это элегантная реализация BNF в C# для образец.
- реализации по умолчанию для типов значений, которые имеют его, полагаются по необходимости на отражение и, следовательно, ужасно неэффективны. Только вы знаете наиболее эффективный способ реализации этих операторов для ваших классов. Во многих случаях не все поля структуры должны сравниваться с тестовым равенством, и не все поля всегда должны быть объединены в подходящую реализацию GetHashCode. Никакая реализация по умолчанию не может определить это для всех типов, потому что это сводится к проблеме остановки.
обновление по состоянию на Эрик Липперт среди прочего, ниже приведена соответствующая стандартная реализация операторов сравнения в C# для типа UDT:
public int CompareTo(UDT x) { return CompareTo(this, x); }
public bool Equals(UDT x) { return CompareTo(this, x) == 0; }
public static bool operator < (UDT x, UDT y) { return CompareTo(x, y) < 0; }
public static bool operator > (UDT x, UDT y) { return CompareTo(x, y) > 0; }
public static bool operator <= (UDT x, UDT y) { return CompareTo(x, y) <= 0; }
public static bool operator >= (UDT x, UDT y) { return CompareTo(x, y) >= 0; }
public static bool operator == (UDT x, UDT y) { return CompareTo(x, y) == 0; }
public static bool operator != (UDT x, UDT y) { return CompareTo(x, y) != 0; }
public override bool Equals(object obj)
{
return (obj is UDT) && (CompareTo(this, (UDT)obj) == 0);
}
просто добавьте пользовательское определение для private static int CompareTo(UDT x, UDT y)
и перемешать.