实现IEqualityComparer 以比较任何类的任意属性(包括匿名)
我正在编写实现IEqualityComparer的这个整洁的类,因此我可以将任何匿名类型传递给它(或实际上任何具有属性的类型),它将通过比较类型的属性值自动比较类型。
public class CompareProperty : IEqualityComparer { private Type type; private PropertyInfo propInfo; private string _fieldName; public string fieldName { get; set; } public CompareProperty(string fieldName) { this.fieldName = fieldName; } public bool Equals(T x, T y) { if (this.type == null) { type = x.GetType(); propInfo = type.GetProperty(fieldName); } object objX = propInfo.GetValue(x, null); object objY = propInfo.GetValue(y, null); return objX.ToString() == objY.ToString(); } }
我认为这是一个很好的小助手function,我可以多次使用。
为了使用它,我必须这样做:
var t = typeof(CompareProperty); var g = t.MakeGenericType(infoType.GetType()); var c = g.GetConstructor(new Type[] {String.Empty.GetType()}); var obj = c.Invoke(new object[] {"somePropertyName"});
很公平,但我如何处理它返回的obj变量?
someEnumerable.Distinct(obj);
不同扩展函数的重载不接受这个,因为它没有看到IEqualityComparer类型,它当然只看到一个对象。
someEnumerable.Distinct((t) obj); someEnumerable.Distinct(obj as t);
这也行不通。 未找到类型/名称空间(红色下划线)。
我该怎么做到这一点?
我将首先为非匿名类型提供解决方案,然后将其扩展为匿名类型。 希望它能帮助您理解人们在对您的问题的评论中试图说些什么。
我的“通用” IEqualityComparer<>
看起来像这样:
public class PropertyComparer : IEqualityComparer { private readonly PropertyInfo propertyToCompare; public PropertyComparer(string propertyName) { propertyToCompare = typeof(T).GetProperty(propertyName); } public bool Equals(T x, T y) { object xValue = propertyToCompare.GetValue(x, null); object yValue = propertyToCompare.GetValue(y, null); return xValue.Equals(yValue); } public int GetHashCode(T obj) { object objValue = propertyToCompare.GetValue(obj, null); return objValue.GetHashCode(); } }
假设我们首先要使用非匿名类型,例如Person
:
public class Person { public int Id { get; set; } public string FirstName { get; set; } public string Surname { get; set; } }
用法非常简单:
IEnumerable people = ... ; // some database call here var distinctPeople = people.Distinct(new PropertyComparer ("FirstName"));
如您所见,要使用PropertyComparer
,我们需要指定将要相互比较的类型( T
)实例。 在处理匿名类型时, T
会是什么? 由于它们是在运行时生成的,因此您不能通过直接创建其实例来使用比较器,因为您在编译时不知道T
相反,您需要使用类型推断让C#编译器自己从上下文中推断T
这样做的好方法是编写以下扩展方法:
public static class LinqExtensions { public static IEnumerable WithDistinctProperty (this IEnumerable source, string propertyName) { return source.Distinct(new PropertyComparer (propertyName)); } }
现在它也适用于匿名类型:
var distinctPeople = people .Select(x => new { x.FirstName, x.Surname }) .WithDistinctProperty("FirstName");
突然之间,没有必要指定查询在任何地方处理的确切类型,因为C#编译器足够聪明,可以从上下文中推断出来(在这种情况下,它是从source
参数的类型中提供的)扩展方法)。
希望这会帮助你。
只需添加自己的validation。
class PropertyComparer : IEqualityComparer { private Func _getProperty; public PropertyComparer(Func predicate) { this._getProperty = predicate; } public bool Equals(T x, T y) { return this._getProperty(x).Equals(_getProperty(y)); } public int GetHashCode(T obj) { return this._getProperty(obj).GetHashCode(); } }
具有扩展方法的类:
public static class IEnumerableExtensions { public static IEnumerable DistinctBy (this IEnumerable source, Func predicate) { return source.Distinct(new PropertyComparer(predicate)); } }
用法:
someEnumerable.DistinctBy(i => i.SomeProperty);