代码改变世界

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();

  其中,手动构造表达式树或许有些麻烦,我们可以先写出代码的逻辑,然后在”翻译“到对应的表达式上去,由于时间关系,在此不一一详细介绍了。那么一个通用的逻辑比较器就实现了。

下面给出具体实现,请参考。

View Code
  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

。。。

 

如果您有任何意见或者对上述实现不足,不要吝啬你的留言,我们一起讨论,共同进步!谢谢!^_^