如何使用 IEqualityComparer

我的数据库里有些铃铛也是这个号码。我想得到所有没有重复。我创建了一个比较类来完成这项工作,但是函数的执行造成了函数的大延迟,从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;
}
}
204229 次浏览

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

When implementing interfaces of classes it is important to 阅读 < a href = “ https://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx”rel = “ noReferrer”> document , to know which contract you’re supposed to implement.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,因此不需要转换为 ListAsEnumerable在这里是 还有冗余的,因为在 AddRange中处理结果将导致这种情况。


编写代码而不知道它实际上是做什么的叫做 货物崇拜编程。这是一种令人惊讶的普遍做法。这根本行不通。

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

您应该尝试开发一个 where 子句,以满足您的需求,请参阅 使用带有 LINQtoEntitiesThan 子句的 IEqualityComparer了解更多细节。

试试这个代码:

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

只要编写代码,并实现 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;
}
}

例如: 以 < strong > Numf 区分的 Class _ reglement列表

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

使用现代框架,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,则必须实现 GetHashCode

附言。我不认为任何自定义 Equals 方法可以直接在数据库端使用实体框架(我想您知道这一点,因为您使用 AsEnumable) ,但是对于一般情况来说,这是一个简单得多的方法来执行一个简单的 Equals。

如果事情似乎没有工作(例如在做 ToDictionary 时出现重复键错误) ,在 Equals 中放置一个断点,以确保命中断点,并确保定义了 GetHashCode(使用覆盖关键字)。

如果你想要一个通用的解决方案,为你的类创建一个 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);
}
}

为了提高 struct 的性能,不需要任何装箱。

用法如下:

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

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

  • making the lambda expression optional in the constructor so that full object equality can be checked by default, not just on one of the properties.
  • 操作不同类型的类,甚至包括子对象或嵌套列表的复杂类型。而且不仅仅是对只包含基元类型属性的简单类。
  • 没有考虑到可能的列表容器差异。
  • 在这里,您将发现第一个简单的代码示例只适用于简单类型(仅由原始属性组成的类型) ,第二个示例已经完成(适用于更广泛的类和复杂类型)。

这是我的两便士尝试:

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

我们仍然可以对它进行优化,但是值得一试。