使用IEqualityComparer<T>的推荐最佳实践是什么?

10

我正在寻找现实世界中的最佳实践,了解其他人如何使用复杂的领域实现解决方案。

6个回答

13
任何时候当你考虑使用 IEqualityComparer<T> 时,暂停一下思考是否可以将这个类实现为 IEquatable<T>。如果一个 Product 应该总是通过 ID 进行比较,只需要定义它相等于 ID,这样就可以使用默认的比较器。
话虽如此,仍有几个原因可能需要自定义比较器:
  1. 如果有多种方式可以将类的实例视为相等的。最好的例子是字符串,框架提供了六种不同的比较器在 StringComparer 中。
  2. 如果该类以一种无法定义为 IEquatable<T> 的方式定义。这包括由其他人定义的类和编译器生成的类(特别是匿名类型,默认情况下使用属性逐个比较)。
如果您确实需要比较器,可以使用通用比较器(请参见 DMenT 的答案),但如果需要重用该逻辑,则应将其封装在专用类中。甚至可以通过继承泛型基类声明它:
class ProductByIdComparer : GenericEqualityComparer<ShopByProduct>
{
    public ProductByIdComparer()
        : base((x, y) => x.ProductId == y.ProductId, z => z.ProductId)
    { }
}

在使用方面,应尽可能利用比较器。例如,不要在每个用作字典键的字符串上调用ToLower()(其逻辑将散布在应用程序中),而应声明字典使用不区分大小写的StringComparer。对于接受比较器的LINQ运算符也是如此。但请始终考虑类应具有内在的可比性行为而不是在外部定义。


7
我做了以下操作,我不确定这是否是现实世界最佳实践,但对我而言它很好用。 :)
public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, Boolean> _comparer;
    private Func<T, int> _hashCodeEvaluator;
    public GenericEqualityComparer(Func<T, T, Boolean> comparer)
    {
        _comparer = comparer;
    }

    public GenericEqualityComparer(Func<T, T, Boolean> comparer, Func<T, int> hashCodeEvaluator)
    {
        _comparer = comparer;
        _hashCodeEvaluator = hashCodeEvaluator;
    }

    #region IEqualityComparer<T> Members

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

    public int GetHashCode(T obj)
    {
        if(obj == null) {
            throw new ArgumentNullException("obj");
        }
        if(_hashCodeEvaluator == null) {
            return 0;
        } 
        return _hashCodeEvaluator(obj);
    }

    #endregion
}

然后您可以在您的集合中使用它。
var comparer = new GenericEqualityComparer<ShopByProduct>((x, y) => x.ProductId == y.ProductId);
var current = SelectAll().Where(p => p.ShopByGroup == group).ToList();
var toDelete = current.Except(products, comparer);
var toAdd = products.Except(current, comparer);

如果您需要支持自定义的GetHashCode()功能,请使用另一种构造函数来提供lambda以进行替代计算:
var comparer = new GenericEqualityComparer<ShopByProduct>(
       (x, y) => { return x.ProductId == y.ProductId; }, 
       (x)    => { return x.Product.GetHashCode()}
);

我希望这可以帮到您。 =)

1
问题:不能假设 obj.GetHashCode() 是有意义的。请参见我帖子中的链接:https://dev59.com/LHVD5IYBdhLWcg3wI4CM#4679491 - Peet Brits
1
请勿使用默认的 obj.GetHashCode() 回退。例如比较器:(s1,s2)=>s1.ToLower().Equals(s2.ToLower()) 返回 true,而默认的 string.GetHashCode() 返回 false - ulrichb

5

2
这是MSDN关于IEqualityComparer(非泛型)的解释:
这个接口允许为集合实现自定义的相等比较。也就是说,您可以创建自己的相等定义,并指定该定义与接受IEqualityComparer接口的集合类型一起使用。在.NET Framework中,Hashtable、NameValueCollection和OrderedDictionary集合类型的构造函数接受此接口。
此接口仅支持相等比较。排序和排序比较的自定义由IComparer接口提供。
看起来泛型版本的这个接口执行相同的功能,但用于Dictionary<(Of <(TKey, TValue)>)>集合。
至于使用此接口的最佳实践,我会说最佳实践是在派生或实现类具有类似于上述.NET Framework集合的功能并且要向自己的集合添加相同功能时使用它。这将确保您与.NET框架使用接口的方式一致。
换句话说,如果您正在开发自定义集合,并希望允许消费者控制在许多LINQ和集合相关方法(例如Sort)中使用的相等性,则支持使用此接口。

1
我认为最好的用途是在需要为某个算法插入不同的相等规则时。就像排序算法可能接受一个 IComparer<T> 一样,查找算法可能接受一个 IEqualityComparer<T>

1

这个列表经常使用这个接口,所以你可以使用a.Substract(b)或其他这些好用的函数。

只要记住:如果你的对象不返回相同的哈希码,那么Equals方法就不会被调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接