簡體   English   中英

通用 IEqualityComparer<T> 和 GetHashCode

[英]Generic IEqualityComparer<T> and GetHashCode

對於實現大量 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);
    }
}

看起來不錯,但是每次都真的需要提供一個散列函數嗎? 我知道哈希碼用於將對象放入桶中。 不同的bucket,object不相等,不調用equal。

如果 GetHashCode 返回相同的值,則調用 equals。 (來自: 為什么在覆蓋 Equals 方法時覆蓋 GetHashCode 很重要?

那么會出現什么問題,例如(我聽到很多程序員驚恐地尖叫),GetHashCode 返回一個常量,強制調用 Equal ?

什么都不會出錯,但是在基於哈希表的容器中,在進行查找時,性能大約為 O(1) 到 O(n)。 您最好簡單地將所有內容存儲在一個 List 中,然后強力搜索它以查找滿足相等性的項目。

如果一個常見的用例是根據對象的一個​​屬性比較對象,您可以添加一個額外的構造函數並像這樣實現和調用它:

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);

這將自動使用由屬性實現的哈希。

編輯:更有效和更強大的實現激發了我的 Marc 的評論:

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); 

在 CodeProject - A Generic IEqualityComparer for 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
}  

你的表現會付諸東流。 當在集合數據結構上實現時, DistinctExcept是有效的操作。 通過提供恆定的哈希值,您基本上可以破壞此特征並使用線性搜索強制執行朴素算法。

您需要查看這對於您的數據量是否可以接受。 但是對於稍大的數據集,差異會很明顯。 例如, Except將預期時間為O(n)到為O(n²),它可以是一個大問題增加。

與其提供常量,不如直接調用對象自己的GetHashCode方法? 它可能不會給出一個特別好的值,但它不會比使用常量更糟糕,並且正確性仍將保留,除非對象的GetHashCode方法被覆蓋以返回錯誤的值。

我需要將 Henrik 解決方案重寫為一個實現IEqualityComparer的類,它給出了這個:

    public class GenericEqualityComparer<T,TKey> : IEqualityComparer<T>
    {
        private readonly Func<T, TKey> _keyFunction;

        public GenericEqualityComparer(Func<T, TKey> keyFunction)
        {
            _keyFunction = keyFunction;
        }

        public bool Equals(T x, T y) => EqualityComparer<TKey>.Default.Equals(_keyFunction(x), _keyFunction(y));

        public int GetHashCode(T obj)=> EqualityComparer<TKey>.Default.GetHashCode(_keyFunction(obj));
    }

試試這個代碼:

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();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM