【问题标题】:How to use the IEqualityComparer如何使用 IEqualityComparer
【发布时间】:2011-10-05 09:19:59
【问题描述】:

我的数据库中有一些相同编号的铃铛。我想在不重复的情况下获得所有这些。我创建了一个比较类来完成这项工作,但是函数的执行导致函数的延迟很大,没有区别,从 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;
    }
}

【问题讨论】:

标签: c# linq iequalitycomparer


【解决方案1】:

您的GetHashCode 实现总是返回相同的值。 Distinct 依靠良好的哈希函数来高效工作,因为它在内部构建了一个 hash table

在实现类的接口时,请务必阅读documentation,了解您应该实现哪个合约。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 programming。这是一种令人惊讶的普遍做法。它根本不起作用。

【讨论】:

  • 当 x 或 y 为空时,您的 Equals 失败。
  • @dzendras 与GetHashCode 相同。但是,请注意 documentation of IEqualityComparer&lt;T&gt; 没有指定如何处理 null 参数 - 但文章中提供的示例也不处理 null
  • 哇。 Abomination 是不必要的苛刻。我们在这里是为了互相帮助,而不是侮辱。我想这会让一些人发笑,但我建议删除它。
  • +1 让我在 wiki 上阅读了有关“货物崇拜编程”的信息,然后将我的 Skype 标记行更改为“// 深魔法从这里开始......随后是一些沉重的魔法”。
  • @NeilBenn 你把坦率的建议误认为是粗鲁。由于 OP 接受了答案(而且,我可能会注意到,在一个更严厉的版本中!),他们似乎没有犯同样的错误。我不知道你为什么认为提供建议是粗鲁的,但当你说“这家伙不需要演讲”时你就错了。我非常不同意:讲座需要的,而且它被铭记在心。编写的代码很糟糕,并且基于糟糕的工作实践。不指出这一点将是一种伤害,而且根本没有帮助,因为那时 OP 无法改进它们的工作方式。
【解决方案2】:

试试这个代码:

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

【讨论】:

  • GetHashCode 也需要使用表达式:return _expr.Invoke(obj).GetHashCode(); 用法见this post
  • 如果集合包含空值,这不应该失败吗?然而,在 VS C# Interactive 上的快速实验似乎并没有抛出 null ref 异常!
【解决方案3】:

如果您想要一个通用解决方案,该解决方案基于该类的属性(充当键)为您的类创建 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);
    }
}

为了获得更好的结构性能,没有任何装箱。

用法是这样的:

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

【讨论】:

  • 你能详细说明“没有拳击”的部分吗?与这里的其他解决方案相比,您到底做了什么避免拳击?
  • @Kirikan 这是一个适用于任何类型的通用解决方案(不仅仅是Class_reglement)。并且没有对 object 的强制转换(或任何强制转换),因此没有性能开销。如果类型是结构,这一点尤其重要。
【解决方案4】:

只需代码,实现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;
    }
}

示例: 由 Numf

区分的 Class_reglement 列表
List<Class_reglement> items = items.Distinct(new Class_reglementComparer());

【讨论】:

    【解决方案5】:

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

    • 在构造函数中将 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
    

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

    【讨论】:

    • 我刚刚编辑了一个 GenericComparer 类,该类也适用于其他对象或列表作为嵌套成员的类型。
    • @GertArnold,一部分来自我已经说过的,这应该适用于更广泛的类,包括带有嵌套对象或数组的对象,我真的不知道要添加什么.. . 如果那是你唯一关心的,它不会为你做饭。 :)
    • @GertArnold:我刚刚为您编辑了这个答案的顶部;)带有 Edit 2 部分。我真的认为这从我的代码 sn-p 中很明显。无论如何,我现在要养成这个好习惯,谢谢。
    【解决方案6】:

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

    您应该尝试开发一个满足您要求的 where 子句,请参阅Using an IEqualityComparer with a LINQ to Entities Except clause 了解更多详细信息。

    【讨论】:

      【解决方案7】:

      IEquatable&lt;T&gt; 使用现代框架可以更容易地做到这一点。

      您会得到一个非常简单的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 关键字)。

      【讨论】:

      • 你还需要检查null
      • 我从来没有遇到过这种情况,但下次我会记得这样做的。您在 List 中是否有 null 或类似的内容?
      • .Equals() 方法下,您似乎将other.Hometown 与其自身进行了比较,而不是this.Hometown
      • 糟糕。修正错字:)
      猜你喜欢
      • 2014-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多