假设我们的一个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