Linq的Distinct太不给力了

转载:http://www.cnblogs.com/LoveJenny/archive/2011/08/01/2124233.html

 

假设我们有一个类: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();
作者: LoveJenny
出处: http://www.cnblogs.com/LoveJenny/    
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类: C#
21
0
    (请您对文章做出评价)   
« 上一篇: 快速上手Expression Tree(一):做一做装配脑袋的Expression Tree 习题
» 下一篇: 完全详解--使用Resource实现多语言的支持
posted @ 2011-08-01 19:59 LoveJenny 阅读( 18808) 评论( 34 编辑 收藏

 
#1楼 2011-08-01 20:17 artwl
为什么不用GroupBy呢,用这个简单多了:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 result = products.GroupBy(p => p.Id).Select(
    p=> new
    {
        Id=p.Key,
        Name=p.FirstOrDefault().Name
    });
result.ToList().ForEach(v =>
    {
        Console.WriteLine(v.Id + ":" + v.Name);
    });
http://pic.cnitblog.com/face/u112805.png?id=201209274142
 
#2楼 [ 楼主2011-08-01 20:34 LoveJenny
@天行健 自强不息
Distinct的语意更清楚点,代码容易理解
https://i-blog.csdnimg.cn/blog_migrate/2bf170850f0c293e25b655160826246a.jpeg
 
#3楼 2011-08-01 20:37 Moen
LZ,我觉得这样写更好,可以减少反射带来的性能损失:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class DelegatedEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool > r_EqualityComparer;
    readonly Func<T, int > r_GetHashCode;
    public DelegatedEqualityComparer(Func<T, T, bool > rpEqualityComparer, Func<T, int > rpGetHashCode)
    {
        r_EqualityComparer = rpEqualityComparer;
        r_GetHashCode = rpGetHashCode;
    }
    public bool Equals(T x, T y) { return r_EqualityComparer(x, y); }
    public int GetHashCode(T rpObject) { return r_GetHashCode(rpObject); }
}
/
var rDelegatedEqualityComparer = new DelegatedEqualityComparer<Product>((x, y) => x == null ? y == null : x.Name == y.Name,
                                                                        r => r == null ? 0 : r.Name.GetHashCode());
                                                                                               
var rDistinctProducts = rProducts.Distinct(rDelegatedEqualityComparer).ToArray();
http://pic.cnitblog.com/face/238163/20130205160500.png
 
#4楼 2011-08-01 20:39 Yufei Huang
你想Distinct出来什么结果?
new Product(){ Id="1" },
new Product(){ Id="1" } ??
那product的Name是什么, n1?还是n2?

如果你想要id或者Name:
products.Select(p => p.Id).Distince();
products.Select(p => p.Name).Distince();
http://pic.cnitblog.com/face/150547/20130202100134.png
 
#5楼 [ 楼主2011-08-01 20:41 LoveJenny
@Moen
主要是使用起来不太方便.
https://i-blog.csdnimg.cn/blog_migrate/2bf170850f0c293e25b655160826246a.jpeg
 
#6楼 [ 楼主2011-08-01 20:42 LoveJenny
@Yufei Huang
数据只是做演示用的,在这里的数据基本上没什么意思.
https://i-blog.csdnimg.cn/blog_migrate/2bf170850f0c293e25b655160826246a.jpeg
 
#7楼 2011-08-01 20:44 <Nigel>
不管要Distinct什么结果,反正杜绝反射~

Moen兄的 就足够了
http://pic.cnitblog.com/face/24797/20130930163024.png
 
#8楼 [ 楼主2011-08-01 20:45 LoveJenny
@《YY》
难道你没看到后面使用表达式树的例子吗?
https://i-blog.csdnimg.cn/blog_migrate/2bf170850f0c293e25b655160826246a.jpeg
 
#9楼 2011-08-01 20:46 <Nigel>
引用 LoveJenny:
@《YY》
难道你没看到后面使用表达式树的例子吗?



我看了,这个也是出于性能考虑的,

如果只是贪图方便的话 当然反射肯定好了。

各取所需吧。
http://pic.cnitblog.com/face/24797/20130930163024.png
 
#10楼 2011-08-01 20:58 FJ. Zhou
这个其实很容易解决。这种类明显是实体类,在实现时应该同时覆盖默认的Equals和GetHashCode。这样Distinct就能判断两个对象是否相等来去掉重复的。
http://pic.cnitblog.com/face/u15739.jpg?id=25214341
 
#11楼 2011-08-01 20:59 <Nigel>
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class KeyEqualityComparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool > comparer;
        private readonly Func<T, object > keyExtractor;
        public KeyEqualityComparer(Func<T, object > keyExtractor) :
            this (keyExtractor, null ) { }
        public KeyEqualityComparer(Func<T, T, bool > comparer) :
            this ( null , comparer) { }
        public KeyEqualityComparer(Func<T, object > keyExtractor, Func<T, T, bool > comparer)
        {
            this .keyExtractor = keyExtractor; this .comparer = comparer;
        }
        public bool Equals(T x, T y)
        {
            if (comparer != null )
                return comparer(x, y);
            else
            {
                var valX = keyExtractor(x); if (valX is IEnumerable< object >)
                    return ((IEnumerable< object >)valX).SequenceEqual((IEnumerable< object >)keyExtractor(y));
                return valX.Equals(keyExtractor(y));
            }
        }
        public int GetHashCode(T obj)
        {
            if (keyExtractor == null )
                return obj.ToString().ToLower().GetHashCode();
            else
            {
                var val = keyExtractor(obj);
                if (val is IEnumerable< object >)
                    return ( int )((IEnumerable< object >)val).Aggregate((x, y) => x.GetHashCode() ^ y.GetHashCode()); return val.GetHashCode();
            }
        }
    }
//
var distinctProduct = products.Distinct( new KeyEqualityComparer<Product>(c => c.Id))
http://pic.cnitblog.com/face/24797/20130930163024.png
 
#12楼 2011-08-01 21:01 鹤冲天
用扩展方法解决,非常简单:
var p1 = products.Distinct(p => p.ID)

请参见我的文章: c#扩展方法奇思妙用基础篇八:Distinct 扩展
http://pic.cnitblog.com/face/u80686.jpg
 
#13楼 2011-08-01 22:11 _龙猫
期待Distinct来完成原本不属于它完成的功能(GroupBy),当然会说不给力了。
 
#14楼 2011-08-01 23:55 Jeffrey Zhao
API编写和设计能力不过关啊……
http://pic.cnitblog.com/face/u9419.png
 
#15楼 2011-08-02 08:51 木鱼
个人在这种情况下倾向于让 Product 自己实现 IEquatable 接口。
http://pic.cnitblog.com/face/u24166.jpg
 
#16楼 2011-08-02 09:36 越狱的鱼
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?

Distinct 没这么用的吧。。。
 
#17楼 2011-08-02 09:41 水牛刀刀
这是我认为目前C#唯一不如Java的一个地方,就是C#里不能创建一个实现了某接口的匿名类,Java在这一点上还是比较方便的。C#的LINQ扩展方法中很多地方用到接受IEqualityComparer<T>参数,有的时候为了一次调用,就要写一个comparer。写个通用的又不可避免反射/dynamic/表达式树(不是不可以,就是这一点没有Java舒服)
http://pic.cnitblog.com/face/137253/20130608105504.png
 
#18楼 2011-08-02 09:43 水牛刀刀
@越狱的鱼
引用 越狱的鱼:
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。

谁告诉你这里Distinct会被解析成sql语句了……
http://pic.cnitblog.com/face/137253/20130608105504.png
 
#19楼 2011-08-02 09:59 越狱的鱼
引用 刀 刀:
@越狱的鱼
引用越狱的鱼:
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。

谁告诉你这里Distinct会被解析成sql语句了……


没被被解析成sql语句,为了容易理解,举个例子 :)
 
#20楼 2011-08-02 10:05 田想兵
引用 越狱的鱼:
引用刀 刀:
@越狱的鱼
引用越狱的鱼:
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。

谁告诉你这里Distinct会被解析成sql语句了……


没被被解析成sql语句,为了容易理解,举个例子 :)

弱弱的問一句,Distinct能不能求和?哈哈~~~~~
http://pic.cnitblog.com/face/u111851.jpg
 
#21楼 2011-08-02 10:09 越狱的鱼
引用 田想兵:
引用越狱的鱼:
引用刀 刀:
@越狱的鱼
引用越狱的鱼:
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。

谁告诉你这里Distinct会被解析成sql语句了……


没被被解析成sql语句,为了容易理解,举个例子 :)

弱弱的問一句,Distinct...


我只能说Distinct太不给力了!
 
#22楼 2011-08-02 12:11 testzhangsan
mark
http://pic.cnitblog.com/face/u137106.jpg?id=15112704
 
#23楼 2011-08-02 12:44 鹤冲天
引用 刀 刀:这是我认为目前C#唯一不如Java的一个地方,就是C#里不能创建一个实现了某接口的匿名类,Java在这一点上还是比较方便的。C#的LINQ扩展方法中很多地方用到接受IEqualityComparer<T>参数,有的时候为了一次调用,就要写一个comparer。写个通用的又不可避免反射/dynamic/表达式树(不是不可以,就是这一点没有Java舒服)

c# 中有嵌套类:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Product
{
    public int ID { get ; set ; }
    public string Name { get ; set ; }
    public IEqualityComparer<Product> GetIDComparer()
    {
        return new IDComparer();
    }
    class IDComparer : IEqualityComparer<Product>
    {
        public bool Equals(Product x, Product y)
        {
            return EqualityComparer< int >.Default.Equals(x.ID, y.ID);
        }
        public int GetHashCode(Product obj)
        {
            return EqualityComparer< int >.Default.GetHashCode(obj.ID);
        }
    }
}

用起来不比 Java 的匿名类差
http://pic.cnitblog.com/face/u80686.jpg
 
#24楼 2011-08-02 12:54 水牛刀刀
@鹤冲天
我没说不能做啊,我是说,为了做某次查询,要专门写个类,这个comparer就为了这一个地方用一次。如果可以匿名的话,就很方便。
?
1
2
3
4
products.Distinct( new IEqualityComparer<Product>() {
   public int GetHashCode() { ... }
   public bool Equals(Product p) { ... }
});

不用为了这一次查询去写个comparer类,而且,下次如果逻辑发生修改,不用还定位到Product类那儿去修改逻辑。
http://pic.cnitblog.com/face/137253/20130608105504.png
 
#25楼 2011-08-02 14:55 Ivony...
典型的SQL的思维,,,,,

这就是个GroupBy的问题。SQL的DISTINCT才是行为诡异的的,没有确定的结果。
http://pic.cnitblog.com/face/u14218.jpg
 
#26楼 2011-08-02 21:23 鹤冲天
引用 刀 刀:
@鹤冲天
我没说不能做啊,我是说,为了做某次查询,要专门写个类,这个comparer就为了这一个地方用一次。如果可以匿名的话,就很方便。
?
1
2
3
4
products.Distinct( new IEqualityComparer<Product>() {
   public int GetHashCode() { ... }
   public bool Equals(Product p) { ... }
});

不用为了这一次查询去写个comparer类,而且,下次如果逻辑发生修改,不用还定位到Product类那儿去修改逻辑。

看看我的新随笔: 何止 Linq 的 Distinct 不给力
里面有更简单的创建 IEqualityComparer<Product> 实例的方法。
http://pic.cnitblog.com/face/u80686.jpg
 
#27楼 2011-08-02 22:57 assiwe
引用 Ivony...:
典型的SQL的思维,,,,,

这就是个GroupBy的问题。SQL的DISTINCT才是行为诡异的的,没有确定的结果。

SQL的distinct怎么会诡异了?
 
#28楼 2011-08-03 17:08 大斌锅
引用 Ivony...:
典型的SQL的思维,,,,,

这就是个GroupBy的问题。SQL的DISTINCT才是行为诡异的的,没有确定的结果。

貌似groupby和distinct都是临时表或内存中排序后获得结果,哪里诡异呢?赐教~
 
#29楼 2011-08-04 14:47 Ivony...
引用 BinBeck:
引用Ivony...:
典型的SQL的思维,,,,,

这就是个GroupBy的问题。SQL的DISTINCT才是行为诡异的的,没有确定的结果。

貌似groupby和distinct都是临时表或内存中排序后获得结果,哪里诡异呢?赐教~



SELECT ID, Name FROM Table GROUP BY Name
这个是语法错误对吧?因为有多个Name相同的时候,无法确定ID是什么。
http://pic.cnitblog.com/face/u14218.jpg
 
#30楼 2012-08-28 11:46 2604529
好文章 解决了
http://pic.cnitblog.com/face/u117648.jpg?id=08152557
 
#31楼 2012-12-03 17:14 呢称,唐伯
感谢楼主的文章 解决我的问题
我保存了哦
LINQ to Entities 不识别方法“System.Linq.IQueryable`1。。。。”,因此该方法无法转换为存储表达式。
http://pic.cnitblog.com/face/u365831.jpg?id=30130746
 
#33楼 2014-03-26 14:44 gongde
发现个问题,微软自带的  类字段如果有类似于泛型类型的  distinct 会无效
 
#34楼 2933106 2014/5/6 17:45:07 2014-05-06 17:45 akak123
@天行健 自强不息
group by 数据量大的话就恐怖了

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值