Универсальный IEqualityComparer <T>и GetHashCode - PullRequest
10 голосов
/ 06 мая 2011

Будучи несколько ленивым в отношении реализации множества IEqualityComparers, и учитывая, что я не мог легко редактировать реализации классов сравниваемого объекта, я пошел к следующему, предназначенному для использования с методами расширения Distinct () и Except ().:

public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> compareFunction;
    Func<T, int> hashFunction;

    public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction)
    {
        this.compareFunction = compareFunction;
        this.hashFunction = hashFunction;
    }

    public bool Equals(T x, T y)
    {
        return compareFunction(x, y);
    }

    public int GetHashCode(T obj)
    {
        return hashFunction(obj);
    }
}

Кажется, хорошо, но действительно ли нужно давать хэш-функцию каждый раз ДЕЙСТВИТЕЛЬНО?Я понимаю, что хеш-код используется для помещения объектов в корзины.Разные сегменты, объекты не равны, и функция равенства не вызывается.

Если GetHashCode возвращает одинаковое значение, вызывается функция равенства.(from: Почему важно переопределить GetHashCode, когда переопределен метод Equals? )

Так что может пойти не так, если, например, (и я слышу, как многие программисты кричат ​​в ужасе), GetHashCode возвращает константу, чтобы вызвать вызов Equal? ​​

Ответы [ 5 ]

13 голосов
/ 06 мая 2011

Ничего не пошло бы не так, но в контейнерах на основе хеш-таблиц при поиске вы увеличиваете производительность примерно с O (1) до O (n).Вам было бы лучше просто хранить все в Списке и грубой силой искать в нем предметы, которые соответствуют равенству.

9 голосов
/ 06 мая 2011

Если общий вариант использования сравнивает объекты в соответствии с одним из их свойств, вы можете добавить дополнительный конструктор, реализовать и вызвать его так:

public GenericEqualityComparer(Func<T, object> projection)
{
    compareFunction = (t1, t2) => projection(t1).Equals(projection(t2));
    hashFunction = t => projection(t).GetHashCode();
}

var comaparer = new GenericEqualityComparer( o => o.PropertyToCompare);

При этом автоматически используется хеш, реализованный свойством.

РЕДАКТИРОВАТЬ: более эффективная и надежная реализация вдохновила мои комментарии Марка:

public static GenericEqualityComparer<T> Create<TValue>(Func<T, TValue> projection)
{
    return new GenericEqualityComparer<T>(
        (t1, t2) => EqualityComparer<TValue>.Default.Equals( projection(t1), projection(t2)),
        t => EqualityComparer<TValue>.Default.GetHashCode(projection(t)));
}

var comparer = GenericEqualityComparer<YourObjectType>.Create( o => o.PropertyToCompare); 
1 голос
/ 05 марта 2014

Нашел этот в CodeProject - Общий IEqualityComparer для Linq Distinct () хорошо сделано.

Вариант использования:

IEqualityComparer<Contact> c =  new PropertyComparer<Contact>("Name");
IEnumerable<Contact> distinctEmails = collection.Distinct(c); 

Универсальный IEqualityComparer

public class PropertyComparer<T> : IEqualityComparer<T>
{
    private PropertyInfo _PropertyInfo;

    /// <summary>
    /// Creates a new instance of PropertyComparer.
    /// </summary>
    /// <param name="propertyName">The name of the property on type T 
    /// to perform the comparison on.</param>
    public PropertyComparer(string propertyName)
    {
        //store a reference to the property info object for use during the comparison
        _PropertyInfo = typeof(T).GetProperty(propertyName, 
    BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
        if (_PropertyInfo == null)
        {
            throw new ArgumentException(string.Format("{0} 
        is not a property of type {1}.", propertyName, typeof(T)));
        }
    }

    #region IEqualityComparer<T> Members

    public bool Equals(T x, T y)
    {
        //get the current value of the comparison property of x and of y
        object xValue = _PropertyInfo.GetValue(x, null);
        object yValue = _PropertyInfo.GetValue(y, null);

        //if the xValue is null then we consider them equal if and only if yValue is null
        if (xValue == null)
            return yValue == null;

        //use the default comparer for whatever type the comparison property is.
        return xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        //get the value of the comparison property out of obj
        object propertyValue = _PropertyInfo.GetValue(obj, null);

        if (propertyValue == null)
            return 0;

        else
            return propertyValue.GetHashCode();
    }

    #endregion
}  
1 голос
/ 06 мая 2011

Ваше выступление пойдет на ветер. Distinct и Except являются эффективными операциями при реализации на заданных структурах данных. Предоставляя постоянное значение хеш-функции, вы существенно разрушаете эту характеристику и применяете наивный алгоритм, используя линейный поиск.

Вам необходимо выяснить, приемлемо ли это для вашего объема данных. Но для несколько больших наборов данных разница будет заметной. Например, Except увеличится с ожидаемого времени O ( n ) до O ( n ²), что может иметь большое значение.

Вместо предоставления константы, почему бы просто не вызвать собственный метод GetHashCode объекта? Это может не дать особенно хорошее значение, но оно не может быть хуже, чем использование константы, и правильность все равно будет сохранена, если метод объекта GetHashCode не будет переопределен для возврата неправильных значений.

0 голосов
/ 13 мая 2014

Попробуйте этот код:

public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericCompare(Func<T, object> expr)
    {
        this._expr = expr;
    }
    public bool Equals(T x, T y)
    {
        var first = _expr.Invoke(x);
        var sec = _expr.Invoke(y);
        if (first != null && first.Equals(sec))
            return true;
        else
            return false;
    }
    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }
}

Пример: collection = collection.Except (ExistedDataEles, new GenericCompare (x => x.Id)). ToList ();

...