Почему я не могу использовать customer.Name.contains(smith) в методе IEqualityComparer‹Customer› Equals

Я хочу использовать метод HashSet.Contains, потому что он очень быстрый.

var hashset = new HashSet<Customer>(customers, new CustomerComparer());
var found = hashset.Contains(new Customer{ Id = "1234", Name = "mit" }); // "mit" instead of an equals "smith" in the comparer.

Я ищу несколько свойств объекта клиента.

Мне нужно реализовать интерфейс IEqualityComparer, например:

public class CustomerComparer : IEqualityComparer<Customer>
{
    public bool Equals(Customer x, Customer y)
    {
        return x.Id == y.Id && x.Name.Contains(y.Name);
    }      

    public int GetHashCode(Customer obj)
    {
        return obj.Id.GetHashCode() ^ obj.Name.GetHashCode();
    }
}

Почему метод Equals никогда не срабатывает, если я НЕ использую метод Equals внутри метода Equals CustomerComparer, такого как .Contains?


person Elisabeth    schedule 09.12.2016    source источник
comment
Я обновил свой вопрос! :-)   -  person Elisabeth    schedule 09.12.2016
comment
msdn.microsoft.com/library/ms132154(v=vs.110 ).aspx Equals должен быть симметричным. Ваша реализация - нет.   -  person Henrik    schedule 09.12.2016


Ответы (2)


То, как вы реализовали компаратор равенства, не может работать должным образом. Причина в том, как хэш-набор и компаратор равенства работают внутри. Когда Dictionary или HashSet сравнивают элементы, он сначала вызывает GetHashCode для обоих элементов. Только если эти хэш-коды совпадают, он подтвердит точное совпадение с последующим вызовом Equals, чтобы избежать ложных совпадений в случае конфликта хэш-кодов. Если вы используете свой пример (x.Name = "smith" и y.Name = "mit"), метод GetHashCode будет возвращать разные хэш-коды для каждого элемента, а Equals никогда не вызывается.

Решение в этом случае состоит в том, чтобы использовать только Id для создания хэш-кода. Это немного ухудшит производительность, потому что вам придется чаще вызывать Equals для разрешения коллизии, но это цена, которую вы должны заплатить:

public int GetHashCode(Customer obj)
{
    return obj.Id.GetHashCode() ;
}

Что вы также должны учитывать, что у вас нет гарантии, будет ли ваш существующий элемент x или y. Таким образом, вы должны использовать Contains в обоих направлениях:

public bool Equals(Customer x, Customer y)
{
    return x.Id == y.Id && (x.Name.Contains(y.Name) || y.Name.Contains(x.Name));
}      
person Sefe    schedule 09.12.2016
comment
Извините, Сефе, это недоразумение. Я звоню hashset.contains, этот код просто не опубликован. Я обновляю свой вопрос! - person Elisabeth; 09.12.2016
comment
Я знаю, что мой примерный вопрос может показаться глупым, но: как бы вы назвали hashset.Contains(new Customer { Id = item.Id, Name = mit }); Что касается вашего решения, если свойство Name должно иметь значение mit или test, вы бы вызвали hashset.Contains() 2 TIMES? один раз с mit и другой раз с тестовым значением? И если одно из значений возвращает true, я могу продолжить бизнес-логику... - person Elisabeth; 09.12.2016
comment
@Elisabeth: Это будет самый простой способ. Если вы хотите справиться с этим одним вызовом, решение, которое я могу придумать, состоит в том, чтобы создать новый класс, который позволяет указать идентификатор и список имен (например, с именем CustomerKey). Затем вы должны реализовать интерфейс IEquatable<CustomerKey> в своем классе Customer. Там вы можете реализовать свое сравнение, которое позволяет список имен. - person Sefe; 09.12.2016
comment
Я сделал это и обновил свой вопрос с вашим предложением кода, но метод Equals из IEquatable никогда не срабатывает, и поэтому моих вернувшихся клиентов слишком много ;-) Но я уже знаю игру и переопределяю GetHashCode с помощью Names Array/HashSet как Марк Гравелл предлагает здесь: stackoverflow. com/questions/638761/ НО вы действительно советуете мне использовать IEqualityComparer для HashSet‹Customer› и IEquatable‹CustomerNames› для Customer, чтобы выполнить мои требования? - person Elisabeth; 09.12.2016
comment
@Elisabeth: Вы не сможете позвонить Contains для IEquatable<CustomerNames> на HashSet<Customer>. Вам понадобится HashSet<IEquatable<CustomerNames>>. I would suggest you just return the hash code of ID` (как это сделано в моем ответе), если вы хотите сопоставить частичные имена. Могу ли я посоветовать, это другой вопрос. Если вам не нужна производительность, множественный поиск в хеш-наборе с Customer объектами проще и сработает. Если вам нужно максимизировать производительность, выберите IEquatable<CustomerNames>. - person Sefe; 09.12.2016

Почему метод Equals никогда не срабатывает, если я НЕ использую метод Equals внутри метода Equals CustomerComparer, такого как .Contains?

Метод Equals сработает только в том случае, если в вашей коллекции «клиенты» присутствует хотя бы один элемент с тем же хэш-кодом, что и у объекта Customer, который вы передаете методу Contains набора HashSet. Если вы запустите следующий пример программы, вы увидите, что метод Equals срабатывает:

public static class Program
{
    public class Customer
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }

    public class CustomerComparer : IEqualityComparer<Customer>
    {
        public bool Equals(Customer x, Customer y)
        {
            Console.WriteLine("hit!");
            return x.Id == y.Id && x.Name.Contains(y.Name);
        }

        public int GetHashCode(Customer obj)
        {
            return obj.Id.GetHashCode() ^ obj.Name.GetHashCode();
        }
    }

    public static void Main()
    {
        List<Customer> customers = new List<Customer>()
        {
            new Customer() { Id = "1234", Name = "smith" },
            new Customer() { Id = "1234", Name = "mit" }
        };
        var hashset = new HashSet<Customer>(customers, new CustomerComparer());
        var found = hashset.Contains(new Customer { Id = "1234", Name = "mit" }); // "mit" instead of an equals "smith" in the comparer.
        Console.WriteLine(found); // = true
    }
}

Но если вы удалите второй элемент из списка «клиентов», метод Equals не сработает, поскольку хэш-код клиента «кузнец» в списке имеет другой хэш-код, чем хэш-код клиента «кузнец», который вы передаете в Содержит метод:

List<Customer> customers = new List<Customer>()
        {
            new Customer() { Id = "1234", Name = "smith" }
        };
person mm8    schedule 09.12.2016