如何使用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;
}
}
您的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
,因此不需要转换为List
。AsEnumerable
在这里也是冗余的,因为在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);
仅代码,实现GetHashCode
和NULL
验证:
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_reglement由Numf区分的列表
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关键字(。