用于 Linq Distinct() 的通用 IEqualityComparer






4.83/5 (17投票s)
IEqualityComparer 的实现,可用于按类的某个属性进行比较。
引言
我喜欢 Linq,并且发现自己越来越频繁地使用它,但每次我(重新)发现无法对集合中的类的属性执行 Distinct 过滤时,我总会感到有些恼火。例如,如果我有一个 Contact
对象的列表,并且我想根据电子邮件地址从该列表中提取一个不重复的 Contact
列表。无参的 Distinct()
方法将基于默认相等比较器比较 Contact
对象,但没有快速的方法来指定我想基于电子邮件地址进行比较。本文介绍了一个 IEqualityComparer
的通用实现,该实现可由 Distinct()
用于根据类的属性来比较任何类。
背景
本文假定您对 .NET 集合的 LINQ 扩展有一般的了解。另外,请注意,本文讨论的是 Linq 在内存对象上的操作,而不是 Linq to SQL 或 Linq to Entities 等。
问题
首先,让我们看看我们的示例 Contact
类
public class Contact
{
public string Name {get; set;}
public string EmailAddress { get; set; }
}
没什么特别的,只是一个带有基本属性的类。我们要解决的问题是,如果我们有一个 Contact
对象的列表,其中一些联系人具有相同的电子邮件地址,我们想通过执行类似以下的操作来仅获取不重复的电子邮件地址列表:
IEnumerable<Contact> collection = //retrieve list of Contacts here
IEnumerable<Contact> distinctEmails = collection.Distinct();
但是如果我们这样做,Distinct
将基于默认相等比较器比较 Contact
对象,该比较器将按引用比较它们。在这种情况下,Distinct
将返回我们原始集合中的所有 Contact
(假设它们都是唯一的实例)。
解决方案 1:重写默认相等比较器
让 Linq 操作 EmailAddress
属性的一个解决方案是为 Contact
类重写 Equals
和 GetHashCode
方法,并让它使用 Contact
的 EmailAddress
属性。这将导致无参的 Distinct()
方法使用您的重写。除了这种方法存在细微的复杂性使其难以处理之外,您可能并不总是希望根据 EmailAddress
比较 Contact
对象。您可能还想根据 Name
进行比较。因此,Equals
运算符可能不是最佳解决方案。
解决方案 2:实现 IEqualityComparer<Contact>
Distinct()
方法还有一个重载,允许您指定一个 IEqualityComparer
实现。因此,另一个解决方案是编写一个实现 IEqualityComparer<Contact>
的类,并基于 EmailAddress
属性执行比较。
要做到这一点,我们必须创建我们的比较器类
class ContactEmailComparer : IEqualityComparer<Contact>
{
#region IEqualityComparer<Contact> Members
public bool Equals(Contact x, Contact y)
{
return x.EmailAddress.Equals(y.EmailAddress);
}
public int GetHashCode(Contact obj)
{
return obj.EmailAddress.GetHashCode();
}
#endregion
}
IEqualityComparer<Contact> customComparer = new ContactEmailComparer();
IEnumerable<Contact> distinctEmails = collection.Distinct(customComparer);
这将导致 Distinct()
方法根据我们自定义的 Equals
实现来比较我们的对象,该实现使用 Contact
的 EmailAddress
属性。
通用解决方案
ContactEmailComparer
的实现非常简单,但仅仅是为了获取不重复的电子邮件地址列表似乎做了很多工作。
一个更通用的解决方案是编写一个泛型类,您可以告诉它比较对象的哪个属性。我们将扩展我们的 IEqualityComparer
,使其使用反射来提取指定属性的值,而不是将我们的类限制为单个属性。
这是该类的实现
public class PropertyComparer<T> : IEqualityComparer<T>
{
private PropertyInfo _PropertyInfo;
/// <summary>
/// Creates a new instance of PropertyComparer.
/// </summary>
/// <param name="propertyName">The name of the property on type T
/// to perform the comparison on.</param>
public PropertyComparer(string propertyName)
{
//store a reference to the property info object for use during the comparison
_PropertyInfo = typeof(T).GetProperty(propertyName,
BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
if (_PropertyInfo == null)
{
throw new ArgumentException(string.Format("{0}
is not a property of type {1}.", propertyName, typeof(T)));
}
}
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
//get the current value of the comparison property of x and of y
object xValue = _PropertyInfo.GetValue(x, null);
object yValue = _PropertyInfo.GetValue(y, null);
//if the xValue is null then we consider them equal if and only if yValue is null
if (xValue == null)
return yValue == null;
//use the default comparer for whatever type the comparison property is.
return xValue.Equals(yValue);
}
public int GetHashCode(T obj)
{
//get the value of the comparison property out of obj
object propertyValue = _PropertyInfo.GetValue(obj, null);
if (propertyValue == null)
return 0;
else
return propertyValue.GetHashCode();
}
#endregion
}
现在,要获取不重复的电子邮件地址列表,我们可以这样做:
IEqualityComparer<Contact> customComparer =
new PropertyComparer<Contact>(“EmailAddress”);
IEnumerable<Contact> distinctEmails = collection.Distinct(customComparer);
这个解决方案最好的地方在于它适用于任何属性和任何类型,所以我们不必编写自定义的 IEqualityComparer
,而是可以重用我们的通用 PropertyComparer
。
例如,无需额外工作,我们还可以通过以下方式获取不重复的 Contact
姓名列表:
IEqualityComparer<Contact> customComparer = new PropertyComparer<Contact>(“Name”);
IEnumerable<Contact> distinctEmails = collection.Distinct(customComparer);
增强功能
目前,此实现仅适用于类的 public
属性。很容易将其扩展到检查 public
字段,这将是一个有用的功能。
结论
这段代码真的没什么特别之处。它只是 IEqualityComparer
的一个通用实现,它在其构造函数中接受一个指定属性名称的 string
。但是,对属性执行 Distinct
过滤,我总觉得应该很容易,但事实证明却有点麻烦。这个类让它变得更容易一些,希望您觉得它有用。
历史
- 2010 年 7 月 15 日:初始发布