Предположим, у кого-то есть Список животных, и кто-то хочет сравнить два элемента друг с другом: экземпляр Кота и экземпляр Пса. Если экземпляр Cat спрашивается, совпадает ли он с экземпляром Dog, имеет ли смысл для кошки генерировать исключение InvalidTypeException или просто сказать «Нет, это не равно».
Предполагается, что метод Equals подчиняется двум правилам:
- Взаимность равенства: Для любых X и Y X.Equals (Y) будет истинным тогда и только тогда, когда Y.Equals (X) истинно.
- Принцип подстановки Лискова: если класс Q наследуется от P, операция, которая может быть выполнена с Q, также может быть выполнена с P.
Это вместе означает, что если Q происходит от P, то объект типа P должен иметь возможность вызывать Equals для объекта типа Q, что, в свою очередь, означает, что объект типа Q должен иметь возможность вызывать Равно для объекта типа P. Кроме того, если R также является производным от P, объект типа Q должен иметь возможность вызывать Equals для объекта типа R (независимо от того, связан ли R с Q).
Хотя для реализации всех объектов Equals может не быть строгой необходимости, для всех классов гораздо проще иметь один метод Equals (Object), чем использовать различные методы Equals для разных базовых типов, каждый из которых должен быть переопределен. с идентичной семантикой, чтобы избежать странного поведения.
Редактировать / Добавление
Object.Equals существует для ответа на вопрос: если две ссылки на объекты X и Y, может ли объект X обещать, что внешний код, который не использует ReferenceEquals, Reflection и т. Д., Не сможет показать, что X и Y не ссылаются к тому же экземпляру объекта? Для любого объекта X X.Equals (X) должен быть истинным, поскольку внешний код не может показать, что X не является тем же экземпляром, что и X. Кроме того, если X.Equals (Y) «законно» истинно, Y.Equals (X) ) также должно быть правдой; в противном случае тот факт, что X.Equals (X) (который является истинным) не соответствует Y.Equals (X), будет очевидной разницей, означающей, что X.Equals (Y) должен быть ложным.
Для мелкомутулируемых типов, если X и Y относятся к разным экземплярам объекта, это можно продемонстрировать, мутировав X и наблюдая, произошла ли та же самая мутация в Y (*). Если такую мутацию можно использовать для демонстрации того, что X и Y являются разными экземплярами объекта, то X.Equals (Y) должен вернуть true. Причина того, что два объекта String, содержащие одинаковые символы, будут сообщать о себе равными друг другу, заключается не только в том, что они содержат одни и те же символы во время сравнения, но еще более важно, что если все экземпляры одного были заменены другим, то только код который использовал ReferenceEquals, Reflection или другие подобные трюки, даже заметил бы.
(*) Можно было бы иметь два разных, но неразличимых экземпляра X и Y мелко изменяемого класса, которые оба содержали ссылки друг на друга, так что методы, которые могли бы мутировать один, также мутировали бы другой. Если бы внешний код не мог отделить экземпляры друг от друга, можно на законных основаниях сообщить X.Equals (Y) об истинном (и наоборот). С другой стороны, я не могу представить, чтобы такой класс был более полезным, чем тот, в котором X и Y содержали неизменные ссылки на общий изменяемый объект. Обратите внимание, что X.Equals (Y) не требует, чтобы X и Y были глубоко неизменными; это просто требует, чтобы любые мутации, примененные к X, имели идентичный эффект на Y.