Подтвердить что ты не робот

Почему мне приходится перегружать операторов при реализации CompareTo?

Скажем, у меня есть тип, который реализует IComparable.

Я бы счел разумным ожидать, что операторы ==, !=, >, <, >= и <= будут "просто работать" автоматически, вызывая CompareTo, но вместо этого у меня есть чтобы переопределить их все, если я хочу их использовать.

С точки зрения дизайна языка есть веская причина, по которой это было сделано так? Есть ли случаи, когда вы действительно полезны для A>B вести себя по-другому с Compare(A,B)>0?

4b9b3361

Ответ 1

Вся ситуация досадно. У С# слишком много способов выразить равенство и неравенство:

  • the ==! = > < >= <= операторы (которые являются логически статическими методами)
  • статический метод Equals (который вызывает виртуальный метод), виртуальный метод Equals, метод ReferenceEquals
  • Интерфейсы IComparable и IEquatable

Все они имеют тонко различную семантику и за исключением статического Equals, ни один из них автоматически не использует другое, и ни один из них не имеет того поведения, которое я хочу. Статические методы отправляются на основе типа времени компиляции обоих операндов; методы виртуальных методов/интерфейсов отправляются на основе типа времени выполнения одного из операндов, что делает операцию асимметричной; тип одной стороны больше, чем тип другой.

Я не могу представить, что кто-то думает, что ситуация, в которой мы находимся, велика; без ограничений, это не то, что было бы развито. Но у управляемых дизайнеров языков есть ограничения: CLR не реализует статические методы в контрактах интерфейса или двойной виртуальной диспетчеризации или возможность помещать ограничение оператора на параметр общего типа. И поэтому для решения проблемы равенства/неравенства эволюционировали многочисленные решения.

Я думаю, что дизайнеры CLR и С# вернулись вовремя и расскажут своим прошлым "себе", какие функции должны быть в 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>

Затем вы можете использовать операторы < и == и т.д. для сравнения элементов.

Ответ 2

Две основные причины:

  • Это общая структура для всех операторов. В то время как операторы сравнения никогда не имеют альтернативной семантики, в структуре есть большая утилита, которая допускает очень разные семантики для некоторых других операторов. Внедрение отдельной структуры только для операторов сравнения потребовало бы отключения некоторой другой, вероятно, гораздо более полезной функции. Ознакомьтесь с этой элегантной реализацией BNF на примере С#.
  • Реализации по умолчанию для случая типов значений, которые у него есть, полагаются по необходимости на Reflection и, следовательно, ужасно неэффективны. Только вы действительно знаете наиболее эффективный способ реализации этих операторов для своих классов. Во многих случаях не все поля структуры должны быть сопоставлены с тестовым равенством, и все поля всегда необходимо комбинировать в подходящей реализации GetHashCode. Никакая реализация по умолчанию никогда не может определить, что для всех типов, потому что она сводится к проблеме остановки.

Обновить согласно Эрик Липперт, среди прочих, следующая стандартная реализация операторов сравнения в С# для типа 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) и перемешайте.