Почему я должен * не * переопределять GetHashCode()?

Мой поиск помощника для правильного объединения составных хэш-кодов для GetHashCode(), казалось, вызвал некоторую враждебность. У меня сложилось впечатление, что некоторые разработчики С# не думают, что вам следует переопределять GetHashCode() часто - наверняка некоторые комментаторы, казалось, думали, что библиотека, помогающая получить правильное поведение, будет бесполезной. Такая функциональность считалась достаточно полезной в Java для сообщества Java, чтобы попросить его добавить в JDK, и это теперь в JDK 7.

Есть ли какая-то фундаментальная причина в том, что в С# вам не нужно - или не обязательно - переопределять GetHashCode() (и, соответственно, Equals()) так часто, как в Java? Я часто делаю это с Java, например, всякий раз, когда я создаю тип, который, как я знаю, я хочу сохранить в HashSet или использовать в качестве ключа в HashMap (эквивалентно,.net Dictionary).

Ответ 1

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

Общепринято писать тип для использования в качестве составного ключа для использования в словаре /HashMap. Часто для таких типов вам нужно равенство значений (эквивалентность) в отличие от ссылочного равенства (идентификация), например:

IDictionary<Person, IList<Movie> > moviesByActor; // e.g. initialised from DB
// elsewhere...
Person p = new Person("Chuck", "Norris");
IList<Movie> chuckNorrisMovies = moviesByActor[p];

Здесь, если мне нужно создать новый экземпляр Person для выполнения поиска, мне нужно Person реализовать значение равенства, иначе оно не будет соответствовать существующим записям в словаре, поскольку они имеют различную идентификацию.

Чтобы получить равенство значений, вам необходимо переопределить Equals() и GetHashCode() на обоих языках.

С# structs (типы значений) реализовать равенство ценности для вас (хотя и потенциально неэффективное) и обеспечить последовательную реализацию GetHashCode. Этого может быть достаточно для многих людей, и они не пойдут дальше, чтобы реализовать свою собственную улучшенную версию, если проблемы с производительностью не диктуют иначе.

В Java нет такой встроенной функции языка. Если вы хотите создать тип с семантикой равенства значений для использования в качестве составного ключа, вы должны реализовать equals() и соответственно hashCode() самостоятельно. (Есть сторонние помощники и библиотеки, которые помогут вам сделать это, но ничего не встроено в язык).

Я описал типы значений С# как "потенциально неэффективные" для использования в словаре, потому что:

Ответ 2

Если ваш объект представляет значение или тип, вам следует переопределить GetHashCode() вместе с Equals. Я никогда не переопределяю хэш-коды для классов управления, например "Приложение". Хотя я не вижу причин, почему даже переопределение GetHashCode() в этих обстоятельствах было бы проблемой, поскольку они никогда не будут помешаны индексированию или сопоставлению коллекции.

Пример:

public class ePoint : eViewModel, IEquatable<ePoint>
{
    public double X;

    public double Y;

    // Methods

    #region IEquatable Overrides

    public override bool Equals(object obj)
    {
        if (Object.ReferenceEquals(obj, null)) { return false; }

        if (Object.ReferenceEquals(this, obj)) { return true; }

        if (!(obj is ePoint)) { return false; }

        return Equals((ePoint)obj);
    }

    public bool Equals(ePoint other)
    {
        return X == other.X && Y == other.Y;
    }

    public override int GetHashCode()
    {
        return (int)Math.Pow(X,Y);
    }

    #endregion

Ответ 3

Я написал вспомогательный класс для реализации GetHashCode(), Equals() и CompareTo() с использованием семантики значений из массива свойств.