Почему хеш-коды отличаются, когда два объекта одного типа имеют одинаковые значения?

Насколько я понимаю, GetHashCode вернет одно и то же значение для двух разных экземпляров, которые имеют одинаковые значения. Документация MSDN немного размыта по этому вопросу.

Хеш-код - это числовое значение, которое используется для идентификации объекта во время проверки на равенство.

Если у меня есть два экземпляра одного типа и одинаковых значений, будет ли GetHashCode() возвращать одно и то же значение?

Предполагая, что все значения одинаковы, следующий тест пройдёт или не пройдёт?

SecurityUser имеет только геттеры и сеттеры;

    [TestMethod]
    public void GetHashCode_Equal_Test()
    {
        SecurityUser objA = new SecurityUser(EmployeeName, EmployeeNumber, LastLogOnDate, Status, UserName);
        SecurityUser objB = new SecurityUser(EmployeeName, EmployeeNumber, LastLogOnDate, Status, UserName);

        int hashcodeA = objA.GetHashCode();
        int hashcodeB = objB.GetHashCode();

        Assert.AreEqual<int>(hashcodeA, hashcodeB);
    }


/// <summary>
/// This class represents a SecurityUser entity in AppSecurity.
/// </summary>
public sealed class SecurityUser
{
    #region [Constructor]

    /// <summary>
    /// Initializes a new instance of the <see cref="SecurityUser"/> class using the
    /// parameters passed.
    /// </summary>
    /// <param name="employeeName">The employee name to initialize with.</param>
    /// <param name="employeeNumber">The employee id number to initialize with.</param>
    /// <param name="lastLogOnDate">The last logon date to initialize with.</param>
    /// <param name="status">The <see cref="SecurityStatus"/> to initialize with.</param>
    /// <param name="userName">The userName to initialize with.</param>        
    public SecurityUser(
        string employeeName,
        int employeeNumber,            
        DateTime? lastLogOnDate,
        SecurityStatus status,
        string userName)
    {
        if (employeeName == null)
            throw new ArgumentNullException("employeeName");

        if (userName == null)
            throw new ArgumentNullException("userName");

        this.EmployeeName = employeeName;
        this.EmployeeNumber = employeeNumber;
        this.LastLogOnDate = lastLogOnDate;
        this.Status = status;
        this.UserName = userName;
    }

    #endregion

    #region [Properties]

    /// <summary>
    /// Gets the employee name of the current instance.
    /// </summary>
    public string EmployeeName { get; private set; }

    /// <summary>
    /// Gets the employee id number of the current instance.
    /// </summary>
    public int EmployeeNumber { get; private set; }

    /// <summary>
    /// Gets the last logon date of the current instance.
    /// </summary>
    public DateTime? LastLogOnDate { get; private set; }

    /// <summary>
    /// Gets the userName of the current instance.
    /// </summary>
    public string UserName { get; private set; }

    /// <summary>
    /// Gets the <see cref="SecurityStatus"/> of the current instance.
    /// </summary>
    public SecurityStatus Status { get; private set; }

    #endregion
}

5 ответов

Решение

Хеш-коды, рассчитанные платформой для ваших пользовательских объектов, не обязательно будут идентичными.

Я полагаю, что это связано с тем, что фреймворк не обходит все ваши поля и тому подобное и не вычисляет их хэш-коды, это было бы дорогостоящим занятием для каждого объекта (я могу ошибаться).

Вот почему рекомендуется переопределить Equals() а также GetHashCode() методы на ваши собственные типы.

Смотрите: Переопределение GetHashCode

Из MSDN:

Реализация по умолчанию метода GetHashCode не гарантирует уникальные возвращаемые значения для разных объектов. Кроме того,.NET Framework не гарантирует реализацию по умолчанию метода GetHashCode, и возвращаемое значение будет одинаковым для разных версий.NET Framework. Следовательно, реализация по умолчанию этого метода не должна использоваться в качестве уникального идентификатора объекта для целей хеширования.

Метод GetHashCode может быть переопределен производным типом. Типы значений должны переопределять этот метод, чтобы обеспечить хеш-функцию, подходящую для этого типа, и обеспечить полезное распределение в хеш-таблице. Для уникальности хэш-код должен основываться на значении поля или свойства экземпляра, а не на статическом поле или свойстве.

Это означает, что вы должны переопределить GetHashCode в вашем классе.

Они могут отличаться, если класс SecurityUser хранит идентификатор, который увеличивается для каждого пользователя, которого вы создаете. Если класс использует это для вычисления своего HashCode, они, вероятно, будут другими. Вы не должны полагаться на GetHashCode проверить на равенство между двумя объектами.

Единственное требование для GetHashCode это если objA.Equals(objB), затем objA.GetHashCode() == objB.GetHashCode(),

См. Эту ссылку (раздел "Примечания для разработчиков") для получения подробной информации о реализации GetHashCode()особенно этот пункт:

  • Если два объекта сравниваются как равные, метод GetHashCode для каждого объекта должен возвращать одинаковое значение. Однако, если два объекта не сравниваются как равные, методы GetHashCode для двух объектов не должны возвращать разные значения.

Если GetHashCode() не отменяется в SecurityUser, два HashCode будут отличаться как два объекта objA а также objB ссылки на различные объекты в памяти (как указано new-Ключевое слово).

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

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

Вы можете переопределить это поведение, переопределив GetHashCode() метод в SecurityUser чтобы вычислить хэш из членов вашего класса, а не из самого класса. Убедитесь, что вы также переопределите Equals(), так как эти два метода идут рука об руку. Вы также можете переопределить оператор равенства ==.

Посмотрите принятый ответ в этом посте для хорошего примера реализации GetHashCode(): Какой алгоритм лучше для переопределенного System.Object.GetHashCode?

Другие вопросы по тегам