Минимальный код для равенства в С#
В этом 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
по умолчанию. Таким образом, по крайней мере, я буду чувствовать, что у меня есть защитная сетка, если я ошибочно использую неправильный метод или если я использую библиотечный метод, который использует другой интерфейс равенства.