IEqualityComparer for Linq Distinct() 通用类

根据国外的文章改的IEqualityComparer通用实现方式,由一个属性改为可以比较多个属性。

源代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Com.Jesus.Utility
{
    public class PropertyComparer<T> : IEqualityComparer<T>
    {
        private List<PropertyInfo> _PropertyInfoCollection;
   
    /// <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[] propertyNameCollection)
    {
        this._PropertyInfoCollection = new List<PropertyInfo>();
        foreach (string propertyName in propertyNameCollection)
        {
            //store a reference to the property info object for use during the comparison
            PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
            if (propertyInfo == null)
            {
                throw new ArgumentException("属性名不对");
            }
            else
            {
                this._PropertyInfoCollection.Add(propertyInfo);
            }
        }
    }
   
    #region IEqualityComparer<T> Members
   
    public bool Equals(T x, T y)
    {
        foreach (PropertyInfo propertyInfo in this._PropertyInfoCollection)
        {
            //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 && yValue != null)
            {
                return false;
            }
            else
            {
                //use the default comparer for whatever type the comparison property is.
                if (!xValue.Equals(yValue)) return false;
            }
        }
        return true;
    }
   
    public int GetHashCode(T obj)
    {
        object propertyValue = null;
        foreach (PropertyInfo propertyInfo in this._PropertyInfoCollection)
        {
            propertyValue = propertyInfo.GetValue(obj, null);
            if (propertyValue == null)
                return 0;
        }
        return propertyValue.GetHashCode();
    }
   
    #endregion

    }
}

使用方式:

IEqualityComparer<TemplateInfo> templateComparer = new Com.Jesus.Utility.PropertyComparer<TemplateInfo>(new string[]{"ID"});
return templateCollection.Distinct(templateComparer).ToList(); //templateCollection数据类型为List<TemplateInfo>

 

以下是原文及地址

http://www.codeproject.com/Articles/94272/A-Generic-IEqualityComparer-for-Linq-Distinct  

Introduction

I love Linq and I find myself using it more and more, but I am always mildly annoyed everytime I (re)discover that I can’t do a Distinct filter on a property of the class in my collection. For example, if I have a list ofContact objects and I want to extract from that list a distinct list ofContacts based on their email address. The parameter-less Distinct()method will compare a Contact object based on the default equality comparer, but there is no quick way to specify that I want to compare them based on email address. This article describes a generic implementation of anIEqualityComparer that can be used by Distinct() to compare any class based on a property of that class.

Background

This article assumes that you have a general understanding of LINQ extensions for .NET collections. Also, bear in mind here that this article is discussing Linq operating on in-memory objects, not Linq to SQL or Linq to Entities or anything else like that.

The Problem

First, let's look at our sample Contact class:

public class Contact
{
    public string Name {get; set;}
    public string EmailAddress { get; set; }
}

Nothing fancy there, just a class with some basic properties. And the problem we want to solve is that if we have a list ofContact objects where some contacts have the same email address, we want to get just a distinct list of email addresses by doing something like this:

IEnumerable<Contact> collection = //retrieve list of Contacts here
IEnumerable<Contact> distinctEmails = collection.Distinct();

But if we do this, Distinct will compare Contact objects based on the default equality comparer which will compare them by reference. In this case,Distinct will return all of the Contacts in our original collection (assuming they are all unique instances).

Solution 1: Override Default Equality Comparer

One solution to get Linq operate on the EmailAddress property would be to override theEquals and GetHashCode methods for the Contact class and have it use the EmailAddress property of the Contact. This would cause the parameter-lessDistinct() method to use your override. Besides the fact that this method has subtle complications that make it tricky, you might not always want to compareContact objects based on EmailAddress. You might also sometimes compare them based onName. So the Equals operator may not be the best solution.

Solution 2: Implement IEqualityComparer<Contact>

The Distinct() method also has an overload which allows you to specify anIEqualityComparer implementation. So, another solution is to write a class that implementsIEqualityComparer<Contact> and performs the comparison based on the EmailAddress property.

To do this, we have to create our comparer class:

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

This will cause the Distinct() method to compare our objects based our customEquals implementation which uses the EmailAddress property of theContact.

A Generic Solution

The implementation of the ContactEmailComparer is pretty trivial, but it does seem like a lot of work just to get a distinct list of email addresses.

A more universal solution is to write a generic class where you can tell it which property of your objects to compare on. We will extend ourIEqualityComparer to use reflection to extract the value of a specified property, rather than restricting our class to one property.

Here is an implementation of such a class:

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
}  

Now, to get our distinct list of email addresses, we do this:

IEqualityComparer<Contact> customComparer =
                   new PropertyComparer<Contact>(“EmailAddress”);
IEnumerable<Contact> distinctEmails = collection.Distinct(customComparer);

The best part about this solution is that it will work for any property and any type, so instead of writing a customIEqualityComparer, we can just reuse our generic PropertyComparer.

For example, with no extra work, we can also get a distinct list of Contacts by name by doing this:

IEqualityComparer<Contact> customComparer =  new PropertyComparer<Contact>(“Name”);
  IEnumerable<Contact> distinctEmails = collection.Distinct(customComparer); 

Enhancements

Currently, this implementation only works for public properties on a class. It would be easy to extend it to also inspectpublic fields which would be a useful feature.

Conclusion

There is really nothing very special about this code. It is just a generic implementation ofIEqualityComparer that takes a string specifying a property name in its constructor. But performing aDistinct filter on a property is something I always feel like ought to be really easy but turns out to be sort of a pain. This class makes it a little easier, I hope you find it useful.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值