为何Linq的Distinct实在是不给力

时间:2023-05-11 17:03:18 

假设我们有一个类:Product

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
}
Main函数如下:
static void Main()
{
    List<Product> products = new List<Product>()
    {
        new Product(){ Id="1", Name="n1"},
        new Product(){ Id="1", Name="n2"},
        new Product(){ Id="2", Name="n1"},
        new Product(){ Id="2", Name="n2"},
    };
    var distinctProduct = products.Distinct();
    Console.ReadLine();
}
可以看到distinctProduct 的结果是:

为何Linq的Distinct实在是不给力

因为Distinct 默认比较的是Product对象的引用,所以返回4条数据。
那么如果我们希望返回Id唯一的product,那么该如何做呢?
 
Distinct方法还有另一个重载:
//通过使用指定的 System.Collections.Generic.IEqualityComparer<T> 对值进行比较
//返回序列中的非重复元素。
 public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source,
IEqualityComparer<TSource> comparer);
该重载接收一个IEqualityComparer的参数。
假设要按Id来筛选,那么应该新建类ProductIdComparer 内容如下:
public class ProductIdComparer : IEqualityComparer<Product>
{
    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());
结果如下:

为何Linq的Distinct实在是不给力

现在假设我们要 按照 Name来筛选重复呢?
很明显,需要再添加一个类ProductNameComparer.
那能不能使用泛型类呢??
新建类PropertyComparer<T> 继承IEqualityComparer<T> 内容如下:
public class PropertyComparer<T> : IEqualityComparer<T>
{
    private PropertyInfo _PropertyInfo;
    /// <summary>
    /// 通过propertyName 获取PropertyInfo对象   
    /// </summary>
    /// <param name="propertyName"></param>
    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<T> 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<Product>("Id"));
var distinctProduct = products.Distinct(new PropertyComparer<Product>("Name"));

结果如下:

为何Linq的Distinct实在是不给力

为什么微软不提供PropertyEquality<T> 这个类呢?
按照上面的逻辑,这个类应该没有很复杂啊,细心的同学可以发现PropertyEquality 大量的使用了反射。每次获取属性的值的时候,都在调用
_PropertyInfo.GetValue(x, null);
可想而知,如果要筛选的记录非常多的话,那么性能无疑会受到影响。
为了提升性能,可以使用表达式树将反射调用改为委托调用,
具体代码如下:

public class FastPropertyComparer<T> : IEqualityComparer<T>
{
    private Func<T, Object> getPropertyValueFunc = null;
    /// <summary>
    /// 通过propertyName 获取PropertyInfo对象
    /// </summary>
    /// <param name="propertyName"></param>
    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<T> 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
}

可以看到现在获取值只需要getPropertyValueFunc(obj) 就可以了。
使用的时候:
var distinctProduct = products.Distinct(new FastPropertyComparer<Product>("Id")).ToList();

标签:linq,distinct
0
投稿

猜你喜欢

  • Java在Excel中创建透视表方法解析

    2021-11-03 05:49:45
  • Android开发简易音乐播放器

    2023-12-26 01:07:03
  • 解决try-catch捕获异常信息后Spring事务失效的问题

    2022-11-15 03:17:33
  • Java 中的字符串常量池详解

    2023-08-15 04:43:45
  • C# WPF如何反射加载Geometry几何图形数据图标

    2021-09-06 11:39:08
  • java 文件下载支持中文名称的实例

    2023-03-16 09:02:16
  • Android编程自定义AlertDialog样式的方法详解

    2023-09-26 20:55:15
  • Flink支持哪些数据类型?

    2023-01-15 06:55:43
  • 轻松学习C#的结构和类

    2023-12-10 13:46:19
  • C#实现农历日历的方法

    2022-08-17 21:27:29
  • Java中对话框的弹出方法

    2022-04-24 14:35:52
  • 如何实现自定义SpringBoot的Starter组件

    2023-06-28 14:49:04
  • C语言实现中国象棋

    2022-09-21 23:40:24
  • Java操作MongoDB模糊查询和分页查询

    2023-03-12 20:31:22
  • Android自制精彩弹幕效果

    2023-12-27 04:57:43
  • Android获取手机的版本号等信息的代码

    2021-09-21 06:00:26
  • Android Studio 3.0 新功能全面解析和旧项目适配问题

    2022-09-16 23:53:57
  • Android实现仿通讯录侧边栏滑动SiderBar效果代码

    2021-08-03 21:07:45
  • 详解SpringMVC和MyBatis框架开发环境搭建和简单实用

    2022-03-11 13:54:51
  • Java实现接口的枚举类示例

    2023-06-18 01:22:04
  • asp之家 软件编程 m.aspxhome.com