為何Linq的Distinct實(shí)在是不給力
更新時(shí)間:2013年05月13日 17:56:07 作者:
本篇文章對(duì)Linq的Distinct進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
假設(shè)我們有一個(gè)類:Product
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
}
Main函數(shù)如下:
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 的結(jié)果是:

因?yàn)镈istinct 默認(rèn)比較的是Product對(duì)象的引用,所以返回4條數(shù)據(jù)。
那么如果我們希望返回Id唯一的product,那么該如何做呢?
Distinct方法還有另一個(gè)重載:
//通過(guò)使用指定的 System.Collections.Generic.IEqualityComparer<T> 對(duì)值進(jìn)行比較
//返回序列中的非重復(fù)元素。
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source,
IEqualityComparer<TSource> comparer);
該重載接收一個(gè)IEqualityComparer的參數(shù)。
假設(shè)要按Id來(lái)篩選,那么應(yīng)該新建類ProductIdComparer 內(nèi)容如下:
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();
}
}
使用的時(shí)候,只需要
var distinctProduct = products.Distinct(new ProductIdComparer());
結(jié)果如下:

現(xiàn)在假設(shè)我們要 按照 Name來(lái)篩選重復(fù)呢?
很明顯,需要再添加一個(gè)類ProductNameComparer.
那能不能使用泛型類呢??
新建類PropertyComparer<T> 繼承IEqualityComparer<T> 內(nèi)容如下:
public class PropertyComparer<T> : IEqualityComparer<T>
{
private PropertyInfo _PropertyInfo;
/// <summary>
/// 通過(guò)propertyName 獲取PropertyInfo對(duì)象
/// </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
}
主要是重寫(xiě)的Equals 和GetHashCode 使用了屬性的值比較。
使用的時(shí)候,只需要:
//var distinctProduct = products.Distinct(new PropertyComparer<Product>("Id"));
var distinctProduct = products.Distinct(new PropertyComparer<Product>("Name"));
結(jié)果如下:

為什么微軟不提供PropertyEquality<T> 這個(gè)類呢?
按照上面的邏輯,這個(gè)類應(yīng)該沒(méi)有很復(fù)雜啊,細(xì)心的同學(xué)可以發(fā)現(xiàn)PropertyEquality 大量的使用了反射。每次獲取屬性的值的時(shí)候,都在調(diào)用
_PropertyInfo.GetValue(x, null);
可想而知,如果要篩選的記錄非常多的話,那么性能無(wú)疑會(huì)受到影響。
為了提升性能,可以使用表達(dá)式樹(shù)將反射調(diào)用改為委托調(diào)用,
具體代碼如下:
public class FastPropertyComparer<T> : IEqualityComparer<T>
{
private Func<T, Object> getPropertyValueFunc = null;
/// <summary>
/// 通過(guò)propertyName 獲取PropertyInfo對(duì)象
/// </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
}
可以看到現(xiàn)在獲取值只需要getPropertyValueFunc(obj) 就可以了。
使用的時(shí)候:
var distinctProduct = products.Distinct(new FastPropertyComparer<Product>("Id")).ToList();
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
}
Main函數(shù)如下:
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 的結(jié)果是:

因?yàn)镈istinct 默認(rèn)比較的是Product對(duì)象的引用,所以返回4條數(shù)據(jù)。
那么如果我們希望返回Id唯一的product,那么該如何做呢?
Distinct方法還有另一個(gè)重載:
//通過(guò)使用指定的 System.Collections.Generic.IEqualityComparer<T> 對(duì)值進(jìn)行比較
//返回序列中的非重復(fù)元素。
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source,
IEqualityComparer<TSource> comparer);
該重載接收一個(gè)IEqualityComparer的參數(shù)。
假設(shè)要按Id來(lái)篩選,那么應(yīng)該新建類ProductIdComparer 內(nèi)容如下:
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();
}
}
使用的時(shí)候,只需要
var distinctProduct = products.Distinct(new ProductIdComparer());
結(jié)果如下:

現(xiàn)在假設(shè)我們要 按照 Name來(lái)篩選重復(fù)呢?
很明顯,需要再添加一個(gè)類ProductNameComparer.
那能不能使用泛型類呢??
新建類PropertyComparer<T> 繼承IEqualityComparer<T> 內(nèi)容如下:
public class PropertyComparer<T> : IEqualityComparer<T>
{
private PropertyInfo _PropertyInfo;
/// <summary>
/// 通過(guò)propertyName 獲取PropertyInfo對(duì)象
/// </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
}
主要是重寫(xiě)的Equals 和GetHashCode 使用了屬性的值比較。
使用的時(shí)候,只需要:
//var distinctProduct = products.Distinct(new PropertyComparer<Product>("Id"));
var distinctProduct = products.Distinct(new PropertyComparer<Product>("Name"));
結(jié)果如下:

為什么微軟不提供PropertyEquality<T> 這個(gè)類呢?
按照上面的邏輯,這個(gè)類應(yīng)該沒(méi)有很復(fù)雜啊,細(xì)心的同學(xué)可以發(fā)現(xiàn)PropertyEquality 大量的使用了反射。每次獲取屬性的值的時(shí)候,都在調(diào)用
_PropertyInfo.GetValue(x, null);
可想而知,如果要篩選的記錄非常多的話,那么性能無(wú)疑會(huì)受到影響。
為了提升性能,可以使用表達(dá)式樹(shù)將反射調(diào)用改為委托調(diào)用,
具體代碼如下:
public class FastPropertyComparer<T> : IEqualityComparer<T>
{
private Func<T, Object> getPropertyValueFunc = null;
/// <summary>
/// 通過(guò)propertyName 獲取PropertyInfo對(duì)象
/// </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
}
可以看到現(xiàn)在獲取值只需要getPropertyValueFunc(obj) 就可以了。
使用的時(shí)候:
var distinctProduct = products.Distinct(new FastPropertyComparer<Product>("Id")).ToList();
您可能感興趣的文章:
- 使用distinct在mysql中查詢多條不重復(fù)記錄值的解決辦法
- distinct 多列問(wèn)題結(jié)合group by的解決方法
- sqlserver中distinct的用法(不重復(fù)的記錄)
- 使用GROUP BY的時(shí)候如何統(tǒng)計(jì)記錄條數(shù) COUNT(*) DISTINCT
- oracle sql 去重復(fù)記錄不用distinct如何實(shí)現(xiàn)
- 解析mysql中:單表distinct、多表group by查詢?nèi)コ貜?fù)記錄
- MongoDB教程之聚合(count、distinct和group)
- mongodb中使用distinct去重的簡(jiǎn)單方法
- SQL中distinct的用法(四種示例分析)
- oracle中distinct的用法詳解
- SQL select distinct的使用方法
- 針對(duì)distinct疑問(wèn)引發(fā)的一系列思考
相關(guān)文章
C#實(shí)現(xiàn)過(guò)濾sql特殊字符的方法集合
這篇文章主要介紹了C#實(shí)現(xiàn)過(guò)濾sql特殊字符的方法,以實(shí)例形式分析總結(jié)了C#針對(duì)SQL危險(xiǎn)字符的幾種常用的過(guò)濾技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-11-11
C#構(gòu)建樹(shù)形結(jié)構(gòu)數(shù)據(jù)(全部構(gòu)建,查找構(gòu)建)
這篇文章主要介紹了C#構(gòu)建樹(shù)形結(jié)構(gòu)數(shù)據(jù)(全部構(gòu)建,查找構(gòu)建),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
C#調(diào)用VB進(jìn)行簡(jiǎn)繁轉(zhuǎn)換的方法
這篇文章主要介紹了C#調(diào)用VB進(jìn)行簡(jiǎn)繁轉(zhuǎn)換的方法,通過(guò)調(diào)用VB的動(dòng)態(tài)鏈接庫(kù)實(shí)現(xiàn)繁簡(jiǎn)轉(zhuǎn)換的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-02-02
C# LINQ查詢表達(dá)式及對(duì)應(yīng)LAMBDA表達(dá)式的用法
這篇文章主要介紹了C# LINQ查詢表達(dá)式及對(duì)應(yīng)LAMBDA表達(dá)式的用法,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-04-04
Unity中協(xié)程IEnumerator的使用方法介紹詳解
本文主要介紹了Unity中協(xié)程IEnumerator的使用方法介紹詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
基于數(shù)據(jù)類型轉(zhuǎn)換(裝箱與拆箱)與常量詳解
下面小編就為大家分享一篇基于數(shù)據(jù)類型轉(zhuǎn)換(裝箱與拆箱)與常量詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11

