C# List中根据指定字段去掉重复项Distinct的用法

假设我们的一个List的数据序列化后是这样的:

[
    {
        "Id": 766,
        "UserId": 6,
        "GameId": 1,
        "Content": "AAAAAAAAAAA"
    },
    {
        "Id": 780,
        "UserId": 125,
        "GameId": 28,
        "Content": "BBBBBBBBBBBB"
    },
    {
        "Id": 779,
        "UserId": 6,
        "GameId": 1,
        "Content": "CCCCCCCCCCCCC"
    },
    {
        "Id": 779,
        "UserId": 6,
        "GameId": 1,
        "Content": "CCCCCCCCCCCCC"
    }
]

一般情况下,在List中使用Distinct可以去掉重List里面的重复的元素,但由于 Distinct 默认比较的是List对象的引用,当数据为上面的JSON的这种情况下,Distinct去重后还是原来的数据
如果我们想根据对象里面的某个字段(比如Id)进行去重来返回唯一记录,我们需要这样做:

Distinct方法有一个重载:
//通过使用指定的 System.Collections.Generic.IEqualityComparer 对值进行比较

//返回序列中的非重复元素。
public static IEnumerable Distinct(this IEnumerable source, IEqualityComparer comparer);
该重载接收一个IEqualityComparer的参数
假设要按Id来筛选,那么应该新建类ProductIdComparer 内容如下:

public class ProductIdComparer : IEqualityComparer
{
    public bool Equals(Product x, Product y)
    {
        if (x == null)
            return y == null;
        return x.Id == y.Id;
    }
    public int GetHashCode(Product obj)
    {
        if (obj == null)
            return 0;
        return obj.Id.GetHashCode();
    }
}

使用的时候,只需要:

var distinctProduct = products.Distinct(new ProductIdComparer());

便可以根据Id的值进行去重,现在假设我们要 按照 Name来筛选重复呢?
很明显,需要再添加一个类 ProductNameComparer.
那能不能使用泛型类呢?
新建类PropertyComparer 继承IEqualityComparer 内容如下:

public class PropertyComparer : IEqualityComparer
{
    private PropertyInfo _PropertyInfo;
   
    public PropertyComparer(string propertyName)
    {
        _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 Members
    public bool Equals(T x, T y)
    {
        object xValue = _PropertyInfo.GetValue(x, null);
        object yValue = _PropertyInfo.GetValue(y, null);
        if (xValue == null)
            return yValue == null;
        return xValue.Equals(yValue);
    }
    public int GetHashCode(T obj)
    {
        object propertyValue = _PropertyInfo.GetValue(obj, null);
        if (propertyValue == null)
            return 0;
        else
            return propertyValue.GetHashCode();
    }
    #endregion
}

主要是重写的Equals 和GetHashCode 使用了属性的值比较。
使用的时候,只需要:
//var distinctProduct = products.Distinct(new PropertyComparer(“Id”));
var distinctProduct = products.Distinct(new PropertyComparer(“Name”));

可以发现 PropertyEquality 大量的使用了反射。每次获取属性的值的时候,都在调用 _PropertyInfo.GetValue(x, null);
如果要筛选的记录非常多的话,那么性能无疑会受到影响。
为了提升性能,可以使用表达式树将反射调用改为委托调用,具体代码如下:

public class FastPropertyComparer : IEqualityComparer
{
    private Func<t, object=""> getPropertyValueFunc = null;
 
    public FastPropertyComparer(string propertyName)
    {
        PropertyInfo _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)));
        }
        ParameterExpression expPara = Expression.Parameter(typeof(T), "obj");
        MemberExpression me = Expression.Property(expPara, _PropertyInfo);
        getPropertyValueFunc = Expression.Lambda<func<t, object="">>(me, expPara).Compile();
    }
    #region IEqualityComparer Members
    public bool Equals(T x, T y)
    {
        object xValue = getPropertyValueFunc(x);
        object yValue = getPropertyValueFunc(y);
        if (xValue == null)
            return yValue == null;
        return xValue.Equals(yValue);
    }
    public int GetHashCode(T obj)
    {
        object propertyValue = getPropertyValueFunc(obj);
        if (propertyValue == null)
            return 0;
        else
            return propertyValue.GetHashCode();
    }
    #endregion
}
</func<t,></t,>

可以看到现在获取值只需要getPropertyValueFunc(obj) 就可以了。
使用的时候:

var distinctProduct = products.Distinct(new FastPropertyComparer("Id")).ToList();

2019-08-25