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

Когда == будет переопределено по-другому в .equals?

Я понимаю разницу между == и .equals. Здесь есть много других вопросов, объясняющих разницу в деталях, например. этот: В чем разница между .Equals и == эта: Поразрядное равенство среди многих других.

Мой вопрос: почему у них есть оба (я понимаю, что должна быть очень хорошая причина) - они оба, похоже, делают одно и то же (если не переопределять по-другому).

Когда == будет перегружаться по-другому, как переопределить .equals?

4b9b3361

Ответ 1

Мой вопрос: почему они оба (я понимаю, что должна быть очень хорошая причина)

Если есть веская причина, это еще не объяснено мне. Сравнения равенства в С# - это честный беспорядок, и в моем списке вещей было 9, о которых я сожалею о дизайне С#:

http://www.informit.com/articles/article.aspx?p=2425867

Математически равенство является простейшим отношением эквивалентности и должно подчиняться правилам: x == x всегда должно быть истинным, x == y всегда должно быть таким же, как y == x, x == y и x != y всегда должно быть противоположным если x == y и y == z истинны, то x == z должно быть истинным. Механизмы С# == и Equals не гарантируют ни одно из этих свойств! (Хотя, к счастью, ReferenceEquals гарантирует их все.)

Как отмечает Джон в своем ответе, == отправляется на основе типов времени компиляции обоих операндов, а .Equals(object) и .Equals(T) из IEquatable<T> отправляются на основе типа времени выполнения левого операнда. Почему один из этих механизмов отправки исправлен? Равенство не является предикатом, который поддерживает его левую сторону, поэтому почему некоторые, но не все реализации так делают?

Действительно, то, что мы хотим для определяемого пользователем равенства, является мультиметодом, где типы выполнения обоих операндов имеют одинаковый вес, но это не понятие, существующее в С#.

Хуже того, невероятно распространено, что Equals и == заданы разные семантики - обычно это одно из эталонных равенств, а другое - равенство значений. Нет причин, по которым наивный разработчик знал бы, что было, или что они были разными. Это значительный источник ошибок. И только ухудшается, когда вы понимаете, что GetHashCode и Equals должны согласиться, но == не нужно.

Я разрабатывал новый язык с нуля, и я по какой-то сумасшедшей причине хотел перегружать оператора - чего я не делаю - тогда я бы разработал систему, которая была бы намного, гораздо более простой. Что-то вроде: если вы реализуете IComparable<T> для типа, вы автоматически получаете <, <=, ==, != и т.д., Определенные для вас операторы, и они реализованы так, чтобы они были согласованы, То есть x<=y должен иметь семантику x<y || x==y, а также семантику !(x>y) и что x == y всегда совпадает с y == x и т.д.

Теперь, если ваш вопрос на самом деле:

Как мы попали в этот божественный беспорядок?

Затем я записал некоторые мысли об этом в 2009 году:

https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/

TL;DR: дизайнеры рамок и разработчики языка имеют разные цели и разные ограничения, и иногда они не учитывают эти факторы в своих проектах, чтобы обеспечить последовательный логический опыт на платформе. Это провал процесса проектирования.

Когда == будет перегружаться по-другому, как переопределить .equals?

Я бы никогда этого не сделал, если бы у меня не было очень необычной, очень веской причины. Когда я реализую арифметические типы, я всегда реализую все операторы, чтобы они были согласованы друг с другом.

Ответ 2

== связывается статически во время компиляции, потому что операторы всегда статичны. Вы перегружаете операторов - вы не можете их переопределить. Equals(object) выполняется полиморфно, потому что оно переопределено.

С точки зрения того, когда вы хотите, чтобы они были разными...

Часто ссылочные типы будут переопределять Equals, но не перегружать == вообще. Полезно легко сказать разницу между "этими двумя ссылками, относящимися к одному и тому же объекту", и "эти две ссылки относятся к равным объектам". (Вы можете использовать ReferenceEquals, если необходимо, конечно, и, как указывает Эрик в комментариях, это яснее.) Вы хотите, чтобы на самом деле было ясно, когда вы это делаете, заметьте.

double имеет такое поведение для значений NaN; ==(double, double) всегда будет возвращать false, если либо операнд NaN, даже если они являются теми же NaN. Equals не может сделать этого без аннулирования контракта. (По общему признанию GetHashCode разбивается на разные значения NaN, но это другое дело...)

Я не помню, чтобы когда-либо их реализовывать, чтобы дать разные результаты лично.

Ответ 3

Один случай, который может возникнуть, - это когда у вас есть предыдущая база кода, которая зависит от ссылочного равенства через ==, но вы решили, что хотите добавить проверку равенства значений. Один из способов сделать это - реализовать IEquatable<T>, что здорово, но теперь, что же касается всего того, что существующий код, который принимал только ссылки, был равен? Должен ли унаследованный Object.Equals отличаться от того, как работает IEquatable<T>.Equals? Это нелегко ответить, так как в идеале вы хотите, чтобы все эти функции/операторы действовали согласованным образом.

Для конкретного случая в BCL, где это произошло, посмотрите TimeZoneInfo. В этом конкретном случае == и Object.Equals остались неизменными, но не ясно, что это лучший выбор.


Как и в сторону, один из способов устранения этой проблемы - сделать класс неизменным. В этом случае код с меньшей вероятностью будет нарушен, если ранее полагался на ссылочное равенство, поскольку вы не можете мутировать экземпляр с помощью ссылки и недействить ранее проверенное равенство.

Ответ 4

Как правило, вы хотите, чтобы они делали то же самое, особенно если ваш код будет использоваться кем-либо другим, кроме вас самих и человека рядом с вами. В идеале, для тех, кто использует ваш код, вы хотите придерживаться принципа наименьшего удивления, которое нарушает случайное поведение. Сказав это:

Перегрузка равенства, как правило, плохая идея, если только тип не является неизменным и запечатан. Если вы находитесь на том этапе, когда вы должны задавать вопросы об этом, то вероятность получить его в любом другом случае будет небольшим. Для этого есть много причин:

а. Equals и GetHashCode играют вместе, чтобы активировать словари и хэш-наборы - если у вас есть несогласованная реализация (или если хэш-код изменяется со временем), возможно одно из следующих:

  • Словари/наборы начинают выполнять как эффективные поиски по линейному времени.
  • Элементы теряются в словарях/наборах

В. Что вы на самом деле пытались сделать? Как правило, идентификация объекта в объектно-ориентированном языке является ссылкой. Таким образом, наличие двух равных объектов с разными ссылками - это просто пустая трата памяти. Вероятно, не было необходимости создавать дубликат в первую очередь.

С. То, что вы часто находите, когда начинаете внедрять равенство для объектов, - это то, что вы ищете определение равенства, которое "для определенной цели". Это делает очень плохую идею сжечь ваши единоличные равные для этого - гораздо лучше определить разные EqualityComparers для использования.

Д. Как указывали другие, вы перегружаете операторы, но переопределяете методы. Это означает, что, если операторы не вызывают методы, ужасно забавные и непоследовательные результаты возникают, когда кто-то пытается использовать == и обнаруживает, что неправильный (неожиданный) метод вызывается на неправильном уровне иерархии.