IEqualityComparer‹T› и пользовательский тип

Я пытаюсь сравнить пользовательский тип в двух List<T> и использовать метод Intersect/Except. Равенство определяется тремя полями этого типа. Равенство основано на более чем обычном условии (все поля содержат одинаковые данные). Я реализовал, конечно, IEqualityComparer<T>. Моя проблема в том, что метод GetHashCode() возвращает не равно, если хэш-код не совпадает, и это мне не помогает, поскольку в моем случае это неверно.

Есть ли способ сравнить два пользовательских объекта, когда равенство основано на более чем одном условии, поэтому я могу использовать пересечение/за исключением/отличных и т. д.?

Вот мой код:

public bool Equals(ComparableObject x, ComparableObject y)
{
    if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
        return false;

    if (Object.ReferenceEquals(x, y))
        return true;

    if (x.Var1.Equals(y.Var1) && x.Var3.Equals(y.Var3) && !x.Var2.Equals(y.Var2))
        return false;

    if (x.Var1.Equals(y.Var1) && !x.Var3.Equals(y.Var3) && !x.Var2.Equals(y.Var2))
        return true;


    if (!x.Var1.Equals(y.Var1) && x.Var3.Equals(y.Var3) && !x.Var2.Equals(y.Var2))
        return false;

    if (!x.Var1.Equals(y.Var1) && x.Var3.Equals(y.Var3) && x.Var2.Equals(y.Var2))
        return true;

    if (x.Var1.Equals(y.Var1) && !x.Var3.Equals(y.Var3) && x.Var2.Equals(y.Var2))
        return false;

    if (!x.Var1.Equals(y.Var1) && !x.Var3.Equals(y.Var3) && x.Var2.Equals(y.Var2))
        return false;

    if (!x.Var1.Equals(y.Var1) && !x.Var3.Equals(y.Var3) && !x.Var2.Equals(y.Var2))
        return false;


    return x.Var1.Equals(y.Var1) && x.Var1.Equals(y.Var1) && x.Var3.Equals(y.Var3);
}


public int GetHashCode(ComparableObject x)
{
    return obj.Var1.GetHashCode() ^ obj.Var2.GetHashCode()^ obj.Var3.GetHashCode()
}

person Bes-m M-bes    schedule 09.03.2012    source источник
comment
Можете ли вы опубликовать свой метод equals? Затем мы можем предложить совместимую GetHashCode реализацию. Или сказать вам, если метод Equals не работает, потому что он нарушает свой контракт.   -  person CodesInChaos    schedule 09.03.2012
comment
Нужны ли одинаковые хеш-коды для сравнения на равенство?   -  person Viacheslav Smityukh    schedule 09.03.2012
comment
@ViacheslavSmityukh Да, это так. IEqualityComparer выполняет работу в два прохода: сначала он собирает хеш-коды для всех объектов, а затем вызывает Equals для пар, где хеш-код был одинаковым. Если бы вместо этого он выполнял наивное сравнение, это привело бы к 500 000 сравнений, если бы вы знали, какие пары объектов из 1000 объектов равны.   -  person GSerg    schedule 09.03.2012


Ответы (2)


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

Например, если одно из трех сравниваемых полей имеет значение int, вы можете вернуть это поле как GetHashCode().

Если же сложно что-то умное придумать, можно вернуть константу, например 42. Таким образом, Equals() будет вызываться для всех пар объектов, предоставляя ожидаемые результаты, хотя и с наименьшей производительностью.

person GSerg    schedule 09.03.2012
comment
-1, потому что он всегда возвращает разные объекты для разных объектов, что, очевидно, невозможно сделать. - person CodesInChaos; 09.03.2012
comment
есть ли риск, если он всегда возвращает -1? - person Bes-m M-bes; 09.03.2012
comment
@ Bes-mM-bes Риска нет, только производительность ниже. - person GSerg; 09.03.2012
comment
Хорошо, в моем случае мне нужно только сравнить некоторые объекты, но я надеюсь, что неожиданных результатов не будет? - person Bes-m M-bes; 09.03.2012

Не уверен, есть ли у вас другие проблемы с GetHashCode в ваших базовых типах, но это пример пользовательского типа и IEqualityComparer, который возвращает true, если совпадают только первые два поля. Это позволит Except и т. д. работать с типом.

    public class CustomType
    {
        public int Val1 { get; set; }
        public int Val2 { get; set; }
        public int Val3 { get; set; }
    }

    class CustomTypeComparer : IEqualityComparer<CustomType>
    {
        public bool Equals(CustomType x, CustomType y)
        { return x.Val1 == y.Val1 && x.Val2 == y.Val2; }

        public int GetHashCode(CustomType obj)
        { return obj.Val1.GetHashCode() ^ obj.Val2.GetHashCode(); }
    }

Если ваши свойства не являются простыми типами, такими как int, вы можете использовать Equals() вместо == для сравнения объектов.

person Joachim Isaksson    schedule 09.03.2012
comment
^ немного не оптимален для многих целей. Обычно используется что-то вроде obj.Val1.GetHashCode() * 37 + obj.Val2.GetHashCode(); - person CodesInChaos; 09.03.2012
comment
@CodeInChaos Да, пример стремился быть компактным, но определенно согласен, есть лучшие способы объединить хэши, чем ^. - person Joachim Isaksson; 09.03.2012
comment
@CodeInChaos это может быть неоптимально, но то, что вы предложили, может привести к сбою, если полученное число не может быть сохранено в Int32. - person Louis Kottmann; 09.03.2012
comment
поэтому вы выполняете его в непроверенном контексте. Используя ключевое слово unchecked или переключатель компилятора. - person CodesInChaos; 09.03.2012