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

Почему Equals() не вызывается для всех объектов при добавлении в коллекцию

У меня есть тип, который я использую как ключ в IDictionary. Тип выглядит следующим образом:

public  class Employee
{
    public string Name { get; set; }
    public int ID { get; set; }

    public override bool Equals(object obj)
    {
        Employee emp = obj as Employee;
        if (emp != null)
            return emp.Name.Equals(this.Name);
        return false;
    }

    public override int GetHashCode()
    {
        return this.Name.GetHashCode();
    }
}

Теперь я создал словарь в качестве следующего в моей статье следующим образом

 IDictionary<Employee, int> empCollection = new Dictionary<Employee, int>();
        Employee emp1 = new Employee() { Name = "abhi", ID = 1 };
        Employee emp2 = new Employee() { Name = "vikram", ID = 2 };
        Employee emp3 = new Employee() { Name = "vikram", ID = 3 };

        empCollection.Add(emp1, 1);
        empCollection.Add(emp2, 2);
        empCollection.Add(emp3, 3);

Теперь при отладке я узнал, что когда emp1 добавлен в коллекцию, только метод GetHashCode вызывается типа ключа, после чего, когда emp2 добавляется в коллекцию, метод GetHashCode снова вызывается, но в случае emp3 оба GetHashCode и методы Equals.

Может быть, это выглядит слишком наивно, задавая этот вопрос, но почему метод Equals не вызывается, когда объект eqImp2 добавляется в коллекцию. Что происходит внутри. Пожалуйста, объясните.

4b9b3361

Ответ 1

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

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

Ваши emp1 и emp2 генерируют разные хэш-коды, поэтому словарю не нужно запускать Equals; он уже знает, что они не равны. С другой стороны, emp2 и emp3 генерируют один и тот же хэш-код, поэтому словарь должен вызывать Equals, чтобы определить, действительно ли они равны, или же идентичный хэш-код был просто результатом случайности.

Ответ 2

В вашем примере GetHashCode просматривается хэш-код имени. emp3 имеет то же имя, что и emp2 ( "викрам" ). Они равны с хэш-кодом, поэтому он выглядит с помощью Equals.

Ответ 3

emp2 и emp3 имеют один и тот же ключ. Это вызовет "ключевое столкновение" в словаре. Сначала он назывался GetHashCode() и определил, что хэш-коды были одинаковыми. Затем он гарантирует, что они одинаковы, вызывая Equals(). Код словаря:

int num = this.comparer.GetHashCode(key) & 2147483647;
...
if (this.entries[i].hashCode == num && this.comparer.Equals(this.entries[i].key, key))

Очевидно, что если хэш-коды не совпадают, ему никогда не нужно вызывать Equals.

Вы должны получить такой инструмент, как ILSpy, а затем вы можете посмотреть код и найти ответ самостоятельно.

Ответ 4

Если вы продолжите этот эксперимент, вы увидите некоторое поведение, специфичное для реализации Dictionary<TKey, TValue>, и некоторое поведение, которое требуется из-за того, как вы реализовали GetHashCode.

Во-первых, важно понять роль GetHashCode и Equals при сравнении объектов для равенства. Дополнительная информация доступна на этом вопросе, но я повторю основные правила здесь:

  • Метод Equals устанавливает точно, какие объекты равны, а какие - нет. Все необходимые проверки должны быть выполнены в этом методе для окончательного определения перед возвратом.
  • Хэш-код - это значение, вычисленное из значения вашего объекта. Обычно он намного меньше исходного объекта (в нашем случае хеш-код является 4-байтным целым) и не обязательно уникальным. Однако гораздо быстрее вычислять и сравнивать друг с другом, чем сами исходные объекты.
    • Когда хэш-коды не обязательно должны быть уникальными, разные хеш-коды указывают разные объекты (т.е. Equals обязательно вернет false), но одинаковые хеш-коды ничего не означают (т.е. Equals может вернуть true или false).

Коллекции, которые связывают значения с ключевым объектом (например, IDictionary<TKey, TValue> в .NET или Map<K, V> в Java), используют хэш-коды для повышения эффективности реализации. Однако, поскольку документация для Object.GetHashCode специально не требует, чтобы результаты были уникальными, эти коллекции не могут полагаться только на хеш-коды для правильного функциональность. Если два объекта имеют один и тот же хэш-код, только вызов Equals может различать их. В этом случае рассматривается случай, описанный для вставки emp3: метод [IDictionary<TKey, TValue>.Add] нужно выбросить ArgumentException, если вы пытаетесь вставить одно и то же значение, и только вызов Equals может определить, совпадает ли новый ключ emp3 с ранее вставленным emp3.

Дополнительные характеристики реализации

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

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

Ответ 5

Это потому, что GetHashCode - это ярлык. Сначала С# вызовет GetHashCode, который должен быстро выполняться. Если два объекта имеют разные HashCodes, тогда нет необходимости вызывать, предположительно, более дорогой метод Equals. Только если они имеют один и тот же HashCode, тогда он вызывается Equals. Это потому, что HashCode не гарантированно будет уникальным