假设我们的一个List的数据序列化后是这样的:
[
{
"Id": 766,
"UserId": 6,
"GameId": 1,
"Content": "AAAAAAAAAAA"
},
{
"Id": 780,
"UserId": 125,
"GameId": 28,
"Content": "BBBBBBBBBBBB"
},
{
"Id": 779,
"UserId": 6,
"GameId": 1,
"Content": "CCCCCCCCCCCCC"
},
{
"Id": 779,
"UserId": 6,
"GameId": 1,
"Content": "CCCCCCCCCCCCC"
}
]
一般情况下,在List中使用Distinct可以去掉重List里面的重复的元素,但由于 Distinct 默认比较的是List对象的引用,当数据为上面的JSON的这种情况下,Distinct去重后还是原来的数据
如果我们想根据对象里面的某个字段(比如Id)进行去重来返回唯一记录,我们需要这样做:
Distinct方法有一个重载:
//通过使用指定的 System.Collections.Generic.IEqualityComparer 对值进行比较
//返回序列中的非重复元素。
public static IEnumerable Distinct(this IEnumerable source, IEqualityComparer comparer);
该重载接收一个IEqualityComparer的参数
假设要按Id来筛选,那么应该新建类ProductIdComparer 内容如下:
public class ProductIdComparer : IEqualityComparer
{
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());
便可以根据Id的值进行去重,现在假设我们要 按照 Name来筛选重复呢?
很明显,需要再添加一个类 ProductNameComparer.
那能不能使用泛型类呢?
新建类PropertyComparer 继承IEqualityComparer 内容如下:
public class PropertyComparer : IEqualityComparer
{
private PropertyInfo _PropertyInfo;
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 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(“Id”));
var distinctProduct = products.Distinct(new PropertyComparer(“Name”));
可以发现 PropertyEquality 大量的使用了反射。每次获取属性的值的时候,都在调用 _PropertyInfo.GetValue(x, null);
如果要筛选的记录非常多的话,那么性能无疑会受到影响。
为了提升性能,可以使用表达式树将反射调用改为委托调用,具体代码如下:
public class FastPropertyComparer : IEqualityComparer
{
private Func<t, object=""> getPropertyValueFunc = null;
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 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
}
</func<t,></t,>
可以看到现在获取值只需要getPropertyValueFunc(obj) 就可以了。
使用的时候:
var distinctProduct = products.Distinct(new FastPropertyComparer("Id")).ToList();
2019-08-25