111

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

7 回答 7

184

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

在实现类的接口时,阅读文档很重要,以了解您应该实现哪个合约。1

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

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

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

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


1编写代码而不知道它实际做什么被称为货物崇拜编程。这是一种令人惊讶的普遍做法。它根本不起作用。

于 2011-07-14T14:11:13.623 回答
52

试试这段代码:

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(); 
于 2014-05-13T06:15:30.933 回答
6

如果您想要一个基于该类的属性(充当键)为您的类创建 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);
于 2018-09-30T09:59:28.347 回答
3

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

  • 使 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

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

于 2022-02-09T10:03:14.637 回答
3

只需代码,执行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());
于 2017-10-23T05:21:04.453 回答
2

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

您应该尝试开发一个满足您要求的 where 子句,有关更多详细信息,请参阅将 IEqualityComparer 与 LINQ to Entities except 子句一起使用。

于 2011-07-14T14:12:14.743 回答
1

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 
    }
}

请注意,GetHashCode如果在字典中或类似Distinct.

PS。我不认为任何自定义 Equals 方法直接在数据库端与实体框架一起使用(我认为您知道这一点是因为您使用 AsEnumerable),但这是一种更简单的方法,可以在一般情况下执行简单的 Equals。

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

于 2018-01-16T21:42:23.013 回答