转载: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();
}
}
使用的时候,只需要
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();
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();
|
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))
|
@越狱的鱼
楼上童鞋们,先不要说API不给力。。。
先看看Distinct用法
var distinctProduct = products.Distinct();
对应生成的sql语句是啥?
select distinct Id,Name from products 结果会是2条?
Distinct 没这么用的吧。。。
谁告诉你这里Distinct会被解析成sql语句了……
没被被解析成sql语句,为了容易理解,举个例子 :)
弱弱的問一句,Distinct能不能求和?哈哈~~~~~
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就为了这一个地方用一次。如果可以匿名的话,就很方便。
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> 实例的方法。
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);
});