□ Симметричность: x.Equals(y) и y.Equals(x) должны возвращать одно и то же значение.

□ Транзитивность: если х. Equals (у) B03BpainaeTtrueny.Equals(z) возвращает true, то х. Equals(z) также должно возвращать true.

□ Постоянство: если в двух сравниваемых значениях не произошло изменений, результат сравнения тоже не должен измениться.

Отступление от этих правил при создании собственной реализации Equals грозит непредсказуемым поведением приложения.

При переопределении метода Equals может потребоваться выполнить несколько дополнительных операций.

□ Реализовать в типе метод Equals интерфейса System.IEquatable<T>. Этот обобщенный интерфейс позволяет определить безопасный в отношении типов метод Equals. Обычно Equals реализуют так, что, принимая параметр типа Object, код метода вызывает безопасный в отношении типов метод Equals.

□ Перегрузить методы операторов == и !=. Обычно код реализации этих операторных методов вызывает безопасный в отношении типов метод Equals.

Если предполагается сравнивать экземпляры собственного типа для целей сортировки, рекомендуется также реализовать метод CompaneTo типа System ЛсотрагаЫе и безопасный в отношении типов метод CompaneTo типа System.IComparable<T>. Реализовав эти методы, можно реализовать метод Equals так, чтобы он вызывал CompaneTo типа System.ICompanable<T> и возвращал tnue, если CompaneTo возвратит 0. После реализации методов CompaneTo также часто требуется перегрузить методы различных операторов сравнения (<, <=, >, >=) и реализовать код этих методов так, чтобы он вызывал безопасный в отношении типов метод CompaneTo.

Хеш-коды объектов

Разработчики FCL решили, что было бы чрезвычайно полезно иметь возможность добавления в хеш-таблицы любых экземпляров любых типов. С этой целью в System.Object включен виртуальный метод GetHashCode, позволяющий вычислить для любого объекта целочисленный (Int32) хеш-код.

Если вы определяете тип и переопределяете метод Equals, вы должны переопределить и метод GetHashCode. Если при определении типа переопределить только один из этих методов, компилятор C# выдаст предупреждение. Например, при компиляции представленного далее кода появится предупреждение: wanning CS0659: 'Pnognam' ovennides Object.Equals(Object о) but does not ovennide Object.GetHashCodeQ ('Pnognam' переопределяет Object.Equals(Object о), но не переопределяет Ob ject.GetHashCode()).

public sealed class Program {

public override Boolean Equals(Object obj) { ... }

>

Причина, по которой в типе должны быть определены оба метода — Equals и GetHashCode, — состоит в том, что реализация типов System.Collections. Hashtable, System. Collections .Generic. Dictionary и любых других коллекций требует, чтобы два равных объекта имели одинаковые значения хеш-кодов. Поэтому, переопределяя Equals, нужно переопределить GetHashCode и обеспечить соответствие алгоритма, применяемого для вычисления равенства, алгоритму, используемому для вычисления хеш-кода объекта.

По сути, когда вы добавляете пару «ключ-значение» в коллекцию, первым вычисляется хеш-код ключа. Он указывает, в каком «сегменте» будет храниться пара «ключ-значение». Когда коллекции требуется найти некий ключ, она вычисляет для него хеш-код. Хеш-код определяет «сегмент» поиска имеющегося в таблице ключа, равного заданному. Применение этого алгоритма хранения и поиска ключей означает, что если вы измените хранящийся в коллекции ключ объекта, коллекция больше не сможет найти этот объект. Если вы намерены изменить ключ объекта в хеш-таблице, то сначала удалите имеющуюся пару «ключ-значение», модифицируйте ключ, а затем добавьте в хеш-таблицу новую пару «ключ-значение».

В определении метода GetHashCode нет особых хитростей. Однако для некоторых типов данных и их распределения в памяти бывает непросто подобрать алгоритм хеширования, который выдавал бы хорошо распределенный диапазон значений.

Вот простой алгоритм, неплохо подходящий для объектов Point:

internal sealed class Point { private readonly Int32 m_x, m_y; public override Int32 GetHashCodeQ {

return m_x Л m_y; // Исключающее ИЛИ для m_x и my

}

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

□ Используйте алгоритм, который дает случайное распределение, повышающее производительность хеш-таблицы.

□ Алгоритм может вызывать метод GetHashCode базового типа и использовать возвращаемое им значение, однако в общем случае лучше отказаться от вызова встроенного метода GetHashCode для типа Object или ValueType, так как эти реализации обладают низкой производительностью алгоритмов хеширования.