[转]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 的结果是:
因为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(); } }
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();
===相关评论, 标记下:
#1楼 2011-08-01 20:17 天行健 自强不息
为什么不用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); }); |
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(); |
你想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();
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();
这个其实很容易解决。这种类明显是实体类,在实现时应该同时覆盖默认的Equals和GetHashCode。这样Distinct就能判断两个对象是否相等来去掉重复的。
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)) |
用扩展方法解决,非常简单:
var p1 = products.Distinct(p => p.ID)
请参见我的文章:c#扩展方法奇思妙用基础篇八:Distinct 扩展
var p1 = products.Distinct(p => p.ID)
请参见我的文章:c#扩展方法奇思妙用基础篇八:Distinct 扩展
API编写和设计能力不过关啊……
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。
这是我认为目前C#唯一不如Java的一个地方,就是C#里不能创建一个实现了某接口的匿名类,Java在这一点上还是比较方便的。C#的LINQ扩展方法中很多地方用到接受IEqualityComparer<T>参数,有的时候为了一次调用,就要写一个comparer。写个通用的又不可避免反射/dynamic/表达式树(不是不可以,就是这一点没有Java舒服)
@越狱的鱼
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。
谁告诉你这里Distinct会被解析成sql语句了……
没被被解析成sql语句,为了容易理解,举个例子 :)
弱弱的問一句,Distinct能不能求和?哈哈~~~~~
@越狱的鱼
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。
谁告诉你这里Distinct会被解析成sql语句了……
没被被解析成sql语句,为了容易理解,举个例子 :)
弱弱的問一句,Distinct...
我只能说Distinct太不给力了!
mark
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 的匿名类差
@鹤冲天
我没说不能做啊,我是说,为了做某次查询,要专门写个类,这个comparer就为了这一个地方用一次。如果可以匿名的话,就很方便。
不用为了这一次查询去写个comparer类,而且,下次如果逻辑发生修改,不用还定位到Product类那儿去修改逻辑。
我没说不能做啊,我是说,为了做某次查询,要专门写个类,这个comparer就为了这一个地方用一次。如果可以匿名的话,就很方便。
1
2
3
4
|
products.Distinct( new IEqualityComparer<Product>() { public int GetHashCode() { ... } public bool Equals(Product p) { ... } }); |
不用为了这一次查询去写个comparer类,而且,下次如果逻辑发生修改,不用还定位到Product类那儿去修改逻辑。
@鹤冲天
我没说不能做啊,我是说,为了做某次查询,要专门写个类,这个comparer就为了这一个地方用一次。如果可以匿名的话,就很方便。
1
2
3
4
|
products.Distinct( new IEqualityComparer<Product>() { public int GetHashCode() { ... } public bool Equals(Product p) { ... } }); |
不用为了这一次查询去写个comparer类,而且,下次如果逻辑发生修改,不用还定位到Product类那儿去修改逻辑。
看看我的新随笔:何止 Linq 的 Distinct 不给力
里面有更简单的创建 IEqualityComparer<Product> 实例的方法。