Минимальный код для равенства в С#

В этом article Эрик Липперт предлагает в пункте 9, что С# имеет "слишком большое равенство". Он указывает, что существует 9 или 10 различных методов или операторов, которые могут быть перегружены для обеспечения равенства объектов.

Мой первый вопрос: если метод Object.Equals(object) переопределен, возможно, компилятор может вызвать любой из других операторов равенства, таких как ==,! =, <= и т.д. без кода, явно выполняет эту операцию?

В С++ существует прецедент для этого поведения. Конструктор копирования может быть вызван компилятором в определенных местах, где необходимо создать временную переменную. Я, по крайней мере, на 95% уверен, что этого не может произойти в С#, но это действительно зависит от того, как компилятор сконструирован и, возможно, и кромки.

Второй вопрос: если компилятор никогда не будет ссылаться на какой-либо из операторов равенства косвенно, тогда было бы хорошо, если бы малый, средний или даже большой проект указывал, что только метод Object.Equals(object) и IEquatable использовать для тестирования равенства, а IComparable - если тип будет использоваться для сортировки или в другое время, когда требуется определить ранг объектов? Другими словами - нормально ли избегать определения других операторов равенства, если все участники проекта согласятся, что они не будут использоваться и, следовательно, не нужны?

Предположим, что код предназначен только для использования в проекте и не будет экспортирован для использования третьими лицами.

Ответ 1

если метод Object.Equals(object) переопределен, возможно ли, чтобы компилятор вызывал любые другие операторы равенства, такие как ==, !=, <= и т.д. без кода, который явно выполняет эту операцию?

Компилятор С# не знает, что Equals и == семантически одинаковы. Если вы вызываете Equals, то этот метод будет вызываться.

тогда было бы хорошо, если бы для небольшого, среднего или даже большого проекта было указано, что для тестирования равенства используется только метод Object.Equals(object) и IEquatable, а IComparable используется, если тип будет использоваться для сортировки или в других случаях при определении ранга объектов? Другими словами - нормально ли избегать определения других операторов равенства, если все участники проекта согласятся, что они не будут использоваться и, следовательно, не нужны?

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

Несколько лет назад я работал над алгоритмом статического анализа, чтобы статистически обнаружить эту ситуацию, и мы обнаружили степень дефекта около двух экземпляров на миллион строк кода во всех изученных нами кодах. При рассмотрении только кодовых баз, которые были где-то переопределены Equals, уровень дефектов был явно значительно выше!

Кроме того, рассмотрим затраты и риски. Если у вас уже есть реализации IComparable, то писать все операторы - тривиальные однострочные, которые не будут иметь ошибок и никогда не будут изменены. Это самый дешевый код, который вы когда-либо собираетесь писать. Если у вас есть выбор между фиксированной стоимостью написания и тестированием десятка крошечных методов против неограниченной стоимости поиска и исправления труднодоступной ошибки, в которой вместо равенства значений используется ссылочное равенство, я знаю, какой из них выбрать.

Ответ 2

Мой первый вопрос: если метод Object.Equals(object) переопределен, возможно, компилятор может вызвать любой из других операторов равенства, таких как ==,! =, <= и т.д. без кода, явно выполняет эту операцию?

Не то, чтобы я знал.

Второй вопрос: если компилятор никогда не будет ссылаться на какой-либо из операторов равенства косвенно, тогда было бы хорошо, если бы малый, средний или даже большой проект указывал, что только метод Object.Equals(object) и IEquatable использовать для тестирования равенства, а IComparable - если тип будет использоваться для сортировки или в другое время, когда требуется определить ранг объектов? Другими словами - нормально ли избегать определения других операторов равенства, если все участники проекта согласятся, что они не будут использоваться и, следовательно, не нужны?

Технически, то, что вы предлагаете, возможно. Но на практике я даже не верю, что буду следовать установленной норме, даже если бы я был единственным, кто работал над проектом. Я также обнаружил, что беспокоюсь за каждый вызов метода LINQ, который я делаю: как этот метод LINQ работает под обложками? Нужно ли IEquatable? Использует ли он IComparable? Я здесь в безопасности?

Если бы я попытался применить аналогичный подход к тому, что вы предлагаете, я бы, вероятно, все еще имел свой класс, реализующий все соответствующие интерфейсы равенства, но методы по умолчанию генерируют исключение NotSupportedException по умолчанию. Таким образом, по крайней мере, я буду чувствовать, что у меня есть защитная сетка, если я ошибочно использую неправильный метод или если я использую библиотечный метод, который использует другой интерфейс равенства.