如何使用IEqualityComparer

本文关键字:IEqualityComparer 何使用 | 更新日期: 2023-09-27 18:02:00

我的数据库中有一些相同编号的铃铛。我想把它们都不重复。我创建了一个比较类来完成这项工作,但函数的执行会导致函数的大延迟,从0.6秒到3.2秒!

我做得对吗?还是必须用另一种方法?

reg.AddRange(
    (from a in this.dataContext.reglements
     join b in this.dataContext.Clients on a.Id_client equals b.Id
     where a.date_v <= datefin && a.date_v >= datedeb
     where a.Id_client == b.Id
     orderby a.date_v descending 
     select new Class_reglement
     {
         nom  = b.Nom,
         code = b.code,
         Numf = a.Numf,
     })
    .AsEnumerable()
    .Distinct(new Compare())
    .ToList());
class Compare : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x.Numf == y.Numf)
        {
            return true;
        }
        else { return false; }
    }
    public int GetHashCode(Class_reglement codeh)
    {
        return 0;
    }
}

如何使用IEqualityComparer

您的GetHashCode实现总是返回相同的值。Distinct依赖于一个好的散列函数来高效地工作,因为它在内部构建了一个散列表。

当实现类的接口时,重要的是阅读文档,以了解您应该实现哪个契约1

在您的代码中,解决方案是将GetHashCode转发到Class_reglement.Numf.GetHashCode,并在那里适当地实现它。

除此之外,您的Equals方法充满了不必要的代码。它可以重写如下(相同的语义,¼的代码,更可读(:

public bool Equals(Class_reglement x, Class_reglement y)
{
    return x.Numf == y.Numf;
}

最后,ToList调用是不必要且耗时的:AddRange接受任何IEnumerable,因此不需要转换为ListAsEnumerable在这里也是冗余的,因为在AddRange中处理结果无论如何都会导致这种情况。


1在不知道实际功能的情况下编写代码称为cargo cult编程。这是一种非常普遍的做法。它根本不起作用。

试试这个代码:

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<DataEle>(x=>x.Id))
    .ToList(); 

如果您想要一个基于类的属性(充当键(为该类创建IEqualityComparer的通用解决方案,请查看以下内容:

public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    private readonly Func<T, TKey> _keyGetter;
    public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
    {
        if (default(T) == null)
        {
            _keyGetter = (x) => x == null ? default : keyGetter(x);
        }
        else
        {
            _keyGetter = keyGetter;
        }
    }
    public bool Equals(T x, T y)
    {
        return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
    }
    public int GetHashCode(T obj)
    {
        TKey key = _keyGetter(obj);
        return key == null ? 0 : key.GetHashCode();
    }
}
public static class KeyBasedEqualityComparer<T>
{
    public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
    {
        return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
    }
}

为了更好地使用structs,没有任何拳击。

用法如下:

IEqualityComparer<Class_reglement> equalityComparer =
  KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf);

仅代码,实现GetHashCodeNULL验证:

public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x is null || y is null))
            return false;
        return x.Numf == y.Numf;
    }
    public int GetHashCode(Class_reglement product)
    {
        //Check whether the object is null 
        if (product is null) return 0;
        //Get hash code for the Numf field if it is not null. 
        int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();
        return hashNumf;
    }
}

示例:Class_reglementNumf区分的列表

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());

此答案的目的是通过以下方式改进以前的答案:

  • 使lambda表达式在构造函数中是可选的,以便在默认情况下可以检查完整的对象相等性,而不仅仅是在其中一个属性上
  • 操作不同类型的类,甚至包括子对象或嵌套列表在内的复杂类型。而且不仅仅是在只包含基元类型属性的简单类上
  • 未考虑可能的列表容器差异
  • 在这里,您将找到第一个仅适用于简单类型(仅由primitif属性组成的类型(的简单代码示例,以及第二个完整的(适用于更广泛的类和复杂类型(

这是我的2便士尝试:

public class GenericEqualityComparer<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericEqualityComparer() => _expr = null;
    public GenericEqualityComparer(Func<T, object> expr) => _expr = expr;
    public bool Equals(T x, T y)
    {
        var first = _expr?.Invoke(x) ?? x;
        var sec = _expr?.Invoke(y) ?? y;
        if (first == null && sec == null)
            return true;
        if (first != null && first.Equals(sec))
            return true;
        var typeProperties = typeof(T).GetProperties();
        foreach (var prop in typeProperties)
        {
            var firstPropVal = prop.GetValue(first, null);
            var secPropVal = prop.GetValue(sec, null);
            if (firstPropVal != null && !firstPropVal.Equals(secPropVal))
                return false;
        }
        return true;
    }
    public int GetHashCode(T obj) =>
        _expr?.Invoke(obj).GetHashCode() ?? obj.GetHashCode();
}

我知道我们仍然可以优化它(也许可以使用递归?(。。但这就像一种魅力,没有那么复杂,适用于广泛的课程。(

编辑:一天后,这是我10美元的尝试:首先,在一个单独的静态扩展类中,您需要:

public static class CollectionExtensions
{
    public static bool HasSameLengthThan<T>(this IEnumerable<T> list, IEnumerable<T> expected)
    {
        if (list.IsNullOrEmptyCollection() && expected.IsNullOrEmptyCollection())
            return true;
        if ((list.IsNullOrEmptyCollection() && !expected.IsNullOrEmptyCollection()) || (!list.IsNullOrEmptyCollection() && expected.IsNullOrEmptyCollection()))
            return false;
        return list.Count() == expected.Count();
    }
    /// <summary>
    /// Used to find out if a collection is empty or if it contains no elements.
    /// </summary>
    /// <typeparam name="T">Type of the collection's items.</typeparam>
    /// <param name="list">Collection of items to test.</param>
    /// <returns><c>true</c> if the collection is <c>null</c> or empty (without items), <c>false</c> otherwise.</returns>
    public static bool IsNullOrEmptyCollection<T>(this IEnumerable<T> list) => list == null || !list.Any();
}

然后,这里是适用于更广泛类的更新类:

public class GenericComparer<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericComparer() => _expr = null;
    public GenericComparer(Func<T, object> expr) => _expr = expr;
    public bool Equals(T x, T y)
    {
        var first = _expr?.Invoke(x) ?? x;
        var sec = _expr?.Invoke(y) ?? y;
        if (ObjEquals(first, sec))
            return true;
        var typeProperties = typeof(T).GetProperties();
        foreach (var prop in typeProperties)
        {
            var firstPropVal = prop.GetValue(first, null);
            var secPropVal = prop.GetValue(sec, null);
            if (!ObjEquals(firstPropVal, secPropVal))
            {
                var propType = prop.PropertyType;
                if (IsEnumerableType(propType) && firstPropVal is IEnumerable && !ArrayEquals(firstPropVal, secPropVal))
                    return false;
                if (propType.IsClass)
                {
                    if (!DeepEqualsFromObj(firstPropVal, secPropVal, propType))
                        return false;
                    if (!DeepObjEquals(firstPropVal, secPropVal))
                        return false;
                }
            }
        }
        return true;
    }
    public int GetHashCode(T obj) =>
        _expr?.Invoke(obj).GetHashCode() ?? obj.GetHashCode();
    #region Private Helpers
    private bool DeepObjEquals(object x, object y) =>
        new GenericComparer<object>().Equals(x, y);
    private bool DeepEquals<U>(U x, U y) where U : class =>
        new GenericComparer<U>().Equals(x, y);
    private bool DeepEqualsFromObj(object x, object y, Type type)
    {
        dynamic a = Convert.ChangeType(x, type);
        dynamic b = Convert.ChangeType(y, type);
        return DeepEquals(a, b);
    }
    private bool IsEnumerableType(Type type) =>
        type.GetInterface(nameof(IEnumerable)) != null;
    private bool ObjEquals(object x, object y)
    {
        if (x == null && y == null) return true;
        return x != null && x.Equals(y);
    }
    private bool ArrayEquals(object x, object y)
    {
        var firstList = new List<object>((IEnumerable<object>)x);
        var secList = new List<object>((IEnumerable<object>)y);
        if (!firstList.HasSameLengthThan(secList))
            return false;
        var elementType = firstList?.FirstOrDefault()?.GetType();
        int cpt = 0;
        foreach (var e in firstList)
        {
            if (!DeepEqualsFromObj(e, secList[cpt++], elementType))
                return false;
        }
        return true;
    }
    #endregion Private Helpers

我们仍然可以优化它,但值得一试^^。

包含比较类(或者更具体地说,需要使用AsEnumerable调用才能使其工作(意味着排序逻辑从基于数据库服务器转变为基于数据库客户端(应用程序(。这意味着您的客户端现在需要检索并处理更多的记录,这总是比在可以使用适当索引的数据库上执行查找效率低。

您应该尝试开发一个满足您需求的where子句,请参阅使用带有LINQ to Entities Except子句的IEqualityComparer以了解更多详细信息。

IEquatable<T>使用现代框架可以更容易地实现这一点。

您得到了一个非常简单的bool Equals(T other)函数,并且不需要麻烦地转换或创建一个单独的类。

public class Person : IEquatable<Person>
{
    public Person(string name, string hometown)
    {
        this.Name = name;
        this.Hometown = hometown;
    }
    public string Name { get; set; }
    public string Hometown { get; set; }
    // can't get much simpler than this!
    public bool Equals(Person other)
    {
        return this.Name == other.Name && this.Hometown == other.Hometown;
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();  // see other links for hashcode guidance 
    }
}

请注意,如果在字典或类似Distinct的东西中使用GetHashCode,则必须实现它。

PS。我不认为任何自定义的Equals方法直接在数据库端与实体框架一起工作(我想你知道这一点,因为你做了AsEnumerable(,但对于一般情况,这是一个更简单的方法来做简单的Equals。

如果事情似乎不起作用(例如在执行ToDictionary时出现重复的键错误(,请在Equals中放置一个断点,以确保它被命中,并确保定义了GetHashCode(带有override关键字(。

相关文章:
  • 没有找到相关文章