zoukankan      html  css  js  c++  java
  • Expression Tree实践之通用《对象相等性比较器GenericEqualityComparer》"让CLR帮我写代码"

      在上篇中,我们已经实现了一个根据类型动态生成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

    。。。

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

  • 相关阅读:
    Delphi中字符串默认以#开头。 dotNET界面
    生成飞库jar手机电子小说ByC# dotNET界面
    CS0016: Could not write to output file 'c:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files/ '拒绝访问' dotNET界面
    SVN备份和还原操作特指Window下使用Visual SVN dotNET界面
    生成飞库jar手机电子小说ByC#[2] dotNET界面
    写了个类似按键精灵的找图类。方便大家做UI测试的时候可以用 dotNET界面
    各种中间件
    聊聊位运算吧
    聊聊设计模式
    腾讯云容器服务(TKE集群)新版本config取不到token问题
  • 原文地址:https://www.cnblogs.com/skysoft001/p/2496893.html
Copyright © 2011-2022 走看看