假设我们有一个类: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 的结果是:

因为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());

结果如下:

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

结果如下:

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

C# 中distinct的使用的更多相关文章

  1. SQL中distinct的用法

    SQL中distinct的用法   1.作用于单列 2.作用于多列 3.COUNT统计 4.distinct必须放在开头 5.其他 在表中,可能会包含重复值.这并不成问题,不过,有时您也许希望仅仅列出 ...

  2. Thinkphp 中 distinct 的用法

    TP中distinct()的用处主要是去除重复的值 在Thinkphp手册中也详细说明了(链接:http://document.thinkphp.cn/manual_3_2.html#distinct ...

  3. MySQL中distinct和group by性能比较[转]

    MySQL中distinct和group by性能比较[转] 之前看了网上的一些测试,感觉不是很准确,今天亲自测试了一番.得出了结论(仅在个人计算机上测试,可能不全面,仅供参考) 测试过程: 准备一张 ...

  4. SQL中distinct的用法和left join查询的含义

    SQL中distinct的用法   1.作用于单列 2.作用于多列 3.COUNT统计 4.distinct必须放在开头 5.其他 在表中,可能会包含重复值.这并不成问题,不过,有时您也许希望仅仅列出 ...

  5. Thinkphp中distinct的用法

    Thinkphp中distincat的用法 TP中distinct()的用处主要是去除重复的值 在Thinkphp手册中也详细说明了(链接:http://document.thinkphp.cn/ma ...

  6. hive中select中DISTINCT的技巧和使用

    hive中select中DISTINCT的技巧和使用 单表的唯一查询用:distinct 多表的唯一查询用:group by 在使用MySQL时,有时需要查询出某个字段不重复的记录,虽然mysql提供 ...

  7. mysql中distinct的用法

    本事例实验用表task,结构如下 MySQL> desc task; +-------------+------------+------+-----+-------------------+- ...

  8. 扩展lamda表达中distinct按照字段去除重复

    首先,我们定义一个Student类来测试. public class Student { public int ID { get; set; } public string Name { get; s ...

  9. SQL中distinct的用法(转自博主:Rain Man)

    在表中,可能会包含重复值.这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值.关键词 distinct用于返回唯一不同的值. 表A: 示例1 select distinct nam ...

随机推荐

  1. javascript模板方法模式

    一:什么是模板方法模式: 模板方法模式由二部分组成,第一部分是抽象父类,第二部分是具体实现的子类,一般的情况下是抽象父类封装了子类的算法框架,包括实现一些公共方法及封装子类中所有方法的执行顺序,子类可 ...

  2. 最诡异的Linux fork进程问题(我们平时都在写)

    从来没有遇到过... 运行环境:在Linux自带的文本编辑器中输入C程序,在shell中编译运行,下面直接看代码和运行结果. 第一个代码:#include<stdio.h> #includ ...

  3. dedecms文章标题是在哪个数据库表?要批量替换关键词

    一位小MM刚接触dedecms没多久还不熟悉后台的操作,她说改dedecms文章中的品牌名改到手酸,问ytkah是否有批量替换关键词的方法,教了她dedecms后台批量替换文章中的关键词方法,她高兴坏 ...

  4. cocos基础教程(7)动作与动画

    动作类(Action) 动作类(Action)是所有动作的基类,它创建的一个对象代表一个动作.动作作用于Node,因此每个动作都需要由Node对象执行.动作类(Action)作为基类,实际上是一个接口 ...

  5. 用LoadRunner实现接口测试

    接口测试的两种方法 其实无论用那种测试方法,接口测试的原理是通过测试程序模拟客户端向服务器发送请求报文,服务器接收请求报文后对相应的报文做出处理然后再把应答报文发送给客户端,客户端接收应答报文这一个过 ...

  6. [BZOJ3624][Apio2008]免费道路

    [BZOJ3624][Apio2008]免费道路 试题描述 输入 输出 输入示例 输出示例 数据规模及约定 见“输入”. 题解 第一步,先尽量加入 c = 1 的边,若未形成一个连通块,则得到必须加入 ...

  7. Linux 怎么查看服务的启动进程所占用的目录

    lsof简介 lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.所以 ...

  8. CSS3弹性盒模型flexbox完整版教程

    http://caibaojian.com/flexbox-guide.html 来自CSS Tricks上的一个教程,原文为:A Complete Guide to Flexbox.文中详细的介绍了 ...

  9. HTML模仿桌面

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. 19.递归法和非递归法反转链表[ReverseLinkedList]

    [题目] 输入一个链表的头结点,反转该链表,并返回反转后链表的头结点. [非递归]  C++ Code  12345678910111213141516   listnode *Reverse_Ite ...