假設我們有一個類: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();