在IEQuality比较器中包装委托

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (21)

几个Linq.可数值函数采用一个IEqualityComparer<T>,是否有一个方便的包装类来适应delegate(T,T)=>bool实施IEqualityComparer<T>是吗?很容易编写一个,但是我想知道是否有一个开箱即用的解决方案。

具体来说,我想做的是将操作设置为DictionaryS,只使用键来定义成员资格(同时根据不同的规则保留值)。

提问于
用户回答回答于

class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

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

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}
用户回答回答于

考虑一下这个扩展方法的含义,如果它使用的全部是Equals方法。如果只有Equals是吗?枚举已经查看过的整个值集合,并检查是否匹配。这将导致Distinct用最坏的O(N2)算法代替O(N)算法!

幸运的是,事实并非如此。Distinct只是使用Equals;它使用GetHashCode也一样。事实上,绝对工作正常而不需要IEqualityComparer<T>提供了一个合适的GetHashCode...。下面是一个人为的例子,说明这一点。

假设我有以下类型:

class Value
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Value(string name, int number)
    {
        Name = name;
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("{0}: {1}", Name, Number);
    }
}

现在说我有一个List<Value>我想找出所有的元素都有一个不同的名字。这是一个完美的用例Distinct使用自定义相等比较器。所以让我们使用Comparer<T>

var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);

现在,如果我们有一群Value具有相同元素的元素Name属性时,它们都应折叠为由Distinct对吧?让我们看看..。

var values = new List<Value>();

var random = new Random();
for (int i = 0; i < 10; ++i)
{
    values.Add("x", random.Next());
}

var distinct = values.Distinct(comparer);

foreach (Value x in distinct)
{
    Console.WriteLine(x);
}

产出:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

GroupBy是吗?

var grouped = values.GroupBy(x => x, comparer);

foreach (IGrouping<Value> g in grouped)
{
    Console.WriteLine("[KEY: '{0}']", g);
    foreach (Value x in g)
    {
        Console.WriteLine(x);
    }
}

产出:

[KEY = 'x: 1346013431']
x: 1346013431
[KEY = 'x: 1388845717']
x: 1388845717
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377

又一次:没起作用。

如果你想一想,这是有意义的Distinct使用HashSet<T>(或同等)内部和GroupBy使用类似于Dictionary<TKey, List<T>>内部。这能解释为什么这些方法不起作用吗?让我们试试这个:

var uniqueValues = new HashSet<Value>(values, comparer);

foreach (Value x in uniqueValues)
{
    Console.WriteLine(x);
}

产出:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

开始有意义了?

希望从这些例子中可以清楚地看出为什么包括一个合适的GetHashCode在任何IEqualityComparer<T>执行是如此重要。

原始答案

这里有一些改进。

  1. 首先,我会Func<T, TKey>而不是Func<T, object>;这将防止实际中的值类型键装箱。keyExtractor本身。
  2. 第二,我实际上要添加一个where TKey : IEquatable<TKey>约束;这将防止在Equals打电话(object.Equals采取object参数;需要一个IEquatable<TKey>执行---采取TKey参数,而不对其进行装箱)。显然,这可能会造成太严重的限制,因此您可以在没有约束的情况下创建一个基类,并使用它创建一个派生类。

下面是生成的代码可能是什么样子:

public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    protected readonly Func<T, TKey> keyExtractor;

    public KeyEqualityComparer(Func<T, TKey> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public virtual bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}

public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
    where TKey : IEquatable<TKey>
{
    public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
        : base(keyExtractor)
    { }

    public override bool Equals(T x, T y)
    {
        // This will use the overload that accepts a TKey parameter
        // instead of an object parameter.
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }
}

扫码关注云+社区