为何Linq的Distinct实在是不给力

 更新时间:2020年6月25日 11:41  点击:1616
假设我们有一个类: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 的结果是:

image

因为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());
结果如下:

image

现在假设我们要 按照 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"));

结果如下:

image

为什么微软不提供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();
[!--infotagslink--]

相关文章

  • C#使用linq对数组进行筛选排序的方法

    这篇文章主要介绍了C#使用linq对数组进行筛选排序的方法,实例分析了C#实用linq扩展进行数组排序的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C#中的Linq Intersect与Except方法使用实例

    这篇文章主要介绍了C#中的Linq Intersect与Except方法使用实例,本文直接给出示例代码,需要的朋友可以参考下...2020-06-25
  • sqlserver中distinct的用法(不重复的记录)

    distinct这个关键字用来过滤掉多余的重复记录只保留一条,但往往只用它来返回不重复记录的条数,而不是用它来返回不重记录的所有值。其原因是distinct只有用二重循环查询来解决,而这样对于一个数据量非常大的站来说,无疑是会直接影响到效率的。...2020-07-11
  • c# Linq常用的小技巧

    这篇文章主要介绍了c# Linq常用的小技巧,文中讲解非常详细,示例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-11-03
  • C# Distinct和重写IEqualityComparer时要知道的二三事

    这篇文章主要给大家介绍了关于C# Distinct和重写IEqualityComparer时要知道的二三事,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
  • 使用linq to xml修改app.config示例(linq读取xml)

    这篇文章主要介绍了使用linq to xml修改app.config示例,需要的朋友可以参考下...2020-06-25
  • C#使用linq查询大数据集的方法

    这篇文章主要介绍了C#使用linq查询大数据集的方法,涉及C#调用linq进行数据查询的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • c#数据绑定之linq使用示例

    本实例以MS AdventureWorks2008Entities数据库为基础,演示了LINQ TO ENTITY、LINQ TO ENTITYSQL和LINQ TO ENTITYCLIENT。...2020-06-25
  • C#使用linq计算执行元素在列表中出现次数的方法

    这篇文章主要介绍了C#使用linq计算执行元素在列表中出现次数的方法,涉及C#使用linq扩展进行列表查询的技巧,需要的朋友可以参考下...2020-06-25
  • Thinkphp 中 distinct 的用法解析

    TP中distinct()的用处主要是去除重复的值,下面我通过实例代码给大家介绍下Thinkphp 中 distinct 的用法,一起看看吧...2017-01-08
  • C#基于Linq和反射实现数据持久化框架Xml4DB详解

    在本篇文章里小编给大家整理的是关于C#基于Linq和反射实现数据持久化框架Xml4DB相关知识点,有需要的朋友们学习下。...2020-06-25
  • C#使用linq语句查询数组中以特定字符开头元素的方法

    这篇文章主要介绍了C#使用linq语句查询数组中以特定字符开头元素的方法,涉及C#使用linq进行查询的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • c# Linq查询详解

    这篇文章主要介绍了c# Linq查询的相关资料,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下...2021-04-13
  • 全面分析c# LINQ

    这篇文章主要介绍了c# LINQ的相关资料,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2020-11-03
  • C#中LINQ多条件JOIN时为什么可以使用匿名类

    这篇文章主要给大家介绍了关于C#中LINQ多条件JOIN时为什么可以使用匿名类的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧...2020-06-25
  • Django 解决distinct无法去除重复数据的问题

    这篇文章主要介绍了Django 解决distinct无法去除重复数据的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-05-20
  • 详解LINQ入门(下篇)

    这篇文章主要介绍了详解LINQ入门(下篇),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#中Linq延迟查询的例子

    这篇文章主要介绍了C#中Linq延迟查询的例子,本文用一个实例来讲解延迟查询的使用,需要的朋友可以参考下...2020-06-25
  • c#中的扩展方法学习笔记

    扩展方法能够向现有类型“添加”方法,而无需创建新的派生类型,重新编译或以其他方式修改原始类型。下面这篇文章主要给大家介绍了关于c#中扩展方法学习的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下...2020-06-25
  • LINQ操作数组代码(交集,并集,差集,最值,平均,去重复)

    数组是大学里经常拿来做算法练习的对象。一些经典算法非常有价值,考试、装逼、面试都十分有用。但现在是效率时代,编程讲究生产效率,利用LINQ,可以让程序猿避免写一些基本算法,把精力花在业务处理上...2021-09-22