Expression Tree实践之通用《对象相等性比较器GenericEqualityComparer》------"让CLR帮我写代码"
2012-05-12 02:03 stevey 阅读(1486) 评论(1) 编辑 收藏 举报在上篇中,我们已经实现了一个根据类型动态生成Parse方法调用。下面我们就实现一个的对象“逻辑”相等的通用比较器,来继续领略一下Expression Tree的动态代码生成的强大功能。
实现这个通用比较器的想法是一个机遇巧合的。那天有个叫小玉的同事问到我这样一个问题,在list列表中怎么样过滤掉重复的对象?他好像是调用别人的存储过程接口,得到可能重复记录的list列表(先不管他这个场景是否合理)。我直接就把msdn关于distinct的方法法给他了,可他感觉好像比较复杂,最终好像是自己写了个for循环的方法实现需求了。刚好过了没多久,就读到老赵在InfoQ上的一篇文章《表达式即编译器》,其中就讲到比较两个对象是否相等通用方法。当时就想到这不是刚才distinct要做的事情吗
我们先来看看如何比较两个对象在“逻辑上”是否相等。假如有类:
public class User { public int UserID { get; set; } public string UserName { get; set; } }
如果两个对象的每个可读属性的值都相等的话,就认为是两个对象逻辑上相等。我们可以使用泛型,通过反射类型的属性的值,来进行比较,这样就”通用“了,但在密集型循环中使用反射,性能是一个问题,如何解决反射带来的性能问题呢?用上篇中提到的Expression Tree,缓存”编译“结果,来优化性能。
代码如下:
1 var x = Expression.Parameter(typeof(T), "x"); 2 var y = Expression.Parameter(typeof(T), "y"); 3 4 var readableProps =from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) 5 where prop.CanRead 6 select prop; 7 // x.p == y.p 8 var equalElements = readableProps.Select(p => (Expression)Expression.Equal(Expression.Property(x, p), Expression.Property(y, p))); 9 var equalFunBody = equalElements.Aggregate((a, b) => Expression.AndAlso(a, b)); 10 var s_equal = Expression.Lambda<Func<T, T, bool>>(equalFunBody, x, y).Compile();
上面将比较两个对象逻辑上是否相等。首先,构造两个参数表达式,然后得到对象的所有可读且public的实例属性名称,equalElements表示属性的比较表达式序列(x.p1==y.p1,x.p2==y.p2,...),通过Aggregate,把这些比较用&&聚合起来(x.p1==y.p1&&x.p2==y.p2&&...),最后构造一个lambdaExpression,并调用其Compile()得到一个委托对象,然后放在 static readonly字段中。
Enumerable的Distinct方法,需要传入一个相等性比较器(IEqualityComparer<T>对象),该接口如下:
// Summary: // Defines methods to support the comparison of objects for equality. // // Type parameters: // T: // The type of objects to compare. public interface IEqualityComparer<T> { // Summary: // Determines whether the specified objects are equal. // // Parameters: // x: // The first object of type T to compare. // // y: // The second object of type T to compare. // // Returns: // true if the specified objects are equal; otherwise, false. bool Equals(T x, T y); // // Summary: // Returns a hash code for the specified object. // // Parameters: // obj: // The System.Object for which a hash code is to be returned. // // Returns: // A hash code for the specified object. // // Exceptions: // System.ArgumentNullException: // The type of obj is a reference type and obj is null. int GetHashCode(T obj); }
Equals方法,我们可以使用上面的方法实现它,那么GetHashCode也可以按照上面的方法去实现。代码如下:
1 // Object.ReferenceEquals(x.A, null)?0:obj.A.GetHashCode() ^ Object.ReferenceEquals(x.B, null)?0:obj.A.GetHashCode() 2 MethodInfo miGetHashCode = typeof(object).GetMethod("GetHashCode"); 3 MethodInfo miReferenceEquals = typeof(object).GetMethod("ReferenceEquals"); 4 //Object.ReferenceEquals(x.A, null)?0:obj.A.GetHashCode() arrays 5 var elements = readableProps.Select(p => 6 { 7 var propertyExp = Expression.Property(x, p);//x.A 8 var testExp = Expression.Call(null, miReferenceEquals, Expression.Convert(propertyExp, typeof(object)), Expression.Constant(null)); 9 10 return (Expression)Expression.Condition( 11 testExp 12 , Expression.Constant(0, typeof(int)) 13 , Expression.Call(propertyExp, miGetHashCode) 14 ); 15 }); 16 //aggregate element by ExclusiveOr 17 var body = elements.Aggregate((a, b) => Expression.ExclusiveOr(a, b)); 18 //create an lambdaExpression and compile to delegate 19 s_hashCode = Expression.Lambda<Func<T, int>>(body, x).Compile();
其中,手动构造表达式树或许有些麻烦,我们可以先写出代码的逻辑,然后在”翻译“到对应的表达式上去,由于时间关系,在此不一一详细介绍了。那么一个通用的逻辑比较器就实现了。
下面给出具体实现,请参考。
1 /// <summary> 2 /// 通用model的逻辑上相等性比较器 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class GenericEqualityComparer<T>:IEqualityComparer<T> 6 { 7 private static readonly Func<T, T, bool> s_equal; 8 9 private static readonly Func<T, int> s_hashCode; 10 11 static GenericEqualityComparer() 12 { 13 var x = Expression.Parameter(typeof(T), "x"); 14 var y = Expression.Parameter(typeof(T), "y"); 15 16 var readableProps =from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) 17 where prop.CanRead 18 select prop; 19 if (readableProps.Count() == 0) 20 { 21 s_equal = (t1, t2) => false; 22 s_hashCode = t1 => 0; 23 } 24 else 25 { 26 #region 动态生成Equals委托 27 // x.p == y.p 28 var equalElements = readableProps.Select(p => (Expression)Expression.Equal(Expression.Property(x, p), Expression.Property(y, p))); 29 var equalFunBody = equalElements.Aggregate((a, b) => Expression.AndAlso(a, b)); 30 s_equal = Expression.Lambda<Func<T, T, bool>>(equalFunBody, x, y).Compile(); 31 32 #endregion 33 34 #region 动态生成GetHashCode的委托 35 36 // Object.ReferenceEquals(x.A, null)?0:obj.A.GetHashCode() ^ Object.ReferenceEquals(x.B, null)?0:obj.A.GetHashCode() 37 MethodInfo miGetHashCode = typeof(object).GetMethod("GetHashCode"); 38 MethodInfo miReferenceEquals = typeof(object).GetMethod("ReferenceEquals"); 39 //Object.ReferenceEquals(x.A, null)?0:obj.A.GetHashCode() arrays 40 var elements = readableProps.Select(p => 41 { 42 var propertyExp = Expression.Property(x, p);//x.A 43 var testExp = Expression.Call(null, miReferenceEquals, Expression.Convert(propertyExp, typeof(object)), Expression.Constant(null)); 44 45 return (Expression)Expression.Condition( 46 testExp 47 , Expression.Constant(0, typeof(int)) 48 , Expression.Call(propertyExp, miGetHashCode) 49 ); 50 }); 51 //aggregate element by ExclusiveOr 52 var body = elements.Aggregate((a, b) => Expression.ExclusiveOr(a, b)); 53 //create an lambdaExpression and compile to delegate 54 s_hashCode = Expression.Lambda<Func<T, int>>(body, x).Compile(); 55 56 #endregion 57 } 58 } 59 60 /// <summary> 61 /// 判断两个model,是否逻辑上相等 62 /// </summary> 63 /// <param name="x"></param> 64 /// <param name="y"></param> 65 /// <returns></returns> 66 public static bool Equals(T x, T y) 67 { 68 //Check whether the compared objects reference the same data. 69 if (Object.ReferenceEquals(x, y)) return true; 70 71 //Check whether any of the compared objects is null. 72 if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) 73 return false; 74 75 //Check whether the products' properties are equal. 76 return s_equal(x, y); 77 } 78 79 /// <summary> 80 /// 获取model对象业务逻辑上的HashCode 81 /// </summary> 82 /// <param name="obj"></param> 83 /// <returns></returns> 84 public static int GetHashCode(T obj) 85 { 86 //Check whether the object is null 87 if (Object.ReferenceEquals(obj, null)) return 0; 88 return s_hashCode(obj); 89 } 90 91 bool IEqualityComparer<T>.Equals(T x, T y) 92 { 93 return Equals(x, y); 94 } 95 96 int IEqualityComparer<T>.GetHashCode(T obj) 97 { 98 return GetHashCode(obj); 99 } 100 }
测试代码如下:
1 List<User> users = new List<User> { 2 new User { UserID=1, UserName="stevey" }, 3 new User { UserID=2, UserName="zhangsan" }, 4 new User { UserID=1, UserName="stevey" }, 5 new User { UserID=3, UserName="jobs" }, 6 null, 7 null, 8 new User { UserID=3, UserName=null }, 9 new User { UserID=3, UserName=null } 10 }; 11 12 var isEqual = SoftLibrary.Helper.GenericEqualityComparer<User>.Equals(users[0], users[2]); 13 Console.WriteLine(isEqual); 14 15 var result = users.Distinct(new SoftLibrary.Helper.GenericEqualityComparer<User>()); 16 foreach (var item in result) 17 { 18 try 19 { 20 Console.WriteLine("UserID:{0},UserName:{1}", item.UserID, item.UserName); 21 } 22 catch 23 { 24 Console.WriteLine("null"); 25 } 26 }
可以正常的工作,使用起来还是很方便的,结果如下:
下面给出最近看到的一些关于Expression Tree的相关地址,非常感谢前辈的文章:
http://msdn.microsoft.com/en-us/library/bb338049.aspx
老赵 的《表达式即编译器》《快速计算表达式》,绝对很有含量哦,膜拜!
装配中的脑袋 的《Expression Tree上手指南》 讲的很详细,易懂
讲的很有深度:LINQ与DLR的Expression tree(1):简介LINQ与Expression tree:http://rednaxelafx.iteye.com/blog/237822
TerryLee的打造自己的LINQ Provider(上):Expression Tree揭秘
鹤冲天的对distinct的思考http://www.cnblogs.com/ldp615/archive/2011/08/02/2125112.html
。。。
如果您有任何意见或者对上述实现不足,不要吝啬你的留言,我们一起讨论,共同进步!谢谢!^_^