表达式树对性能的影响

       至于表达式树是啥我就不想说了,大家可以参考脑袋的相关文章。一至以来,我虽然接触过表达式树,还写过一篇表达式树和泛型委托相关的文章,但终究只现在表面功能,并没有真正认识到它的好处。这两天我看了脑袋的相关文章,每篇都给我们留下了一些练习题,有一道是关于一次性修改实体集中相关属性的问题。

      原题:日常应用中我们常常会和一些数据实体类打交道。现在假设我们的数据实体类里面定义了许多public的属性。请用Expression Tree实现一个动态逻辑,可以更新任意一种数据实体类的集合,比如List<Book>, List<Employee>等等中每一个元素的指定属性。比如我要对一个集合中所有Book元素的Author属性进行更新,您的程序就可以这样调用:

    List<Book> books = …

    SetAllProperty(books, “Author”, “Ninputer”);

    当然您写的方法不知道Book类型,也不知道我要更新哪个属性,它应该能动态地支持任意的实体类集合。

    首先我想到的就是遍历实体的属性,找对就修改的属性,然后调用SetValue方式就OK,代码也不多,如下: 

public List<T> SetAllProperty(List<T> t, object propertyName, object propertyValue)
         {
             
if (null == t||null ==propertyName ||null ==propertyValue )
             {
                 
return null ;
             }
             
for (int i = 0; i < t.Count; i++)
             {
                 PropertyInfo[] proAs 
= t[i].GetType().GetProperties();
                 
foreach (PropertyInfo proA in proAs)
                 {
                     
if (!proA.CanRead &&!proA .CanWrite ) continue;

                     
if (proA.Name.ToLower()==propertyName .ToString ().ToLower ())
                     {
                         proA.SetValue(t[i], propertyValue , 
null);
                     }
                 
                 }
             }
             
return t;
         }

    
      问题:上面的代码理论上和实际操作上都能够很好的完成练习题,但有个性能问题,如果这个实体集特别大的时候,比如一百万或者更多,此时就会特别耗时。于时我在脑袋的指点下,花了大半天时间终于有所结果:

      版本一:结果都是正确的,问题时,这种写法比遍历属性赋值方式更慢。问题就在于LambdaExpression lambda = Expression.Lambda(call, parameter, value);var exp = lambda.Compile();这两条语句上,这个表达式经过Compile()后会生成一个Delegate,Compile方法在内部使用了Emit,而问题就出了DynamicInvoke上面,DynamicInvoke方法其本质与反射调用差不多。

public List<T> ExpressionTree<T>(List<T> collection, object propertyName, string   propertyValue)
        {
            ParameterExpression parameter 
= Expression.Parameter(typeof(T), "x");

            ParameterExpression value 
= Expression.Parameter(typeof(string  ), "propertyValue");

            MethodInfo setter 
= typeof(T).GetMethod("set_"+propertyName );

            MethodCallExpression call 
= Expression.Call(parameter, setter, value);

            LambdaExpression lambda 
= Expression.Lambda(call, parameter, value);
            var exp 
= lambda.Compile();
            
for (int i = 0; i < collection.Count; i++)
            {
                exp.DynamicInvoke(collection[i], propertyValue);

            }
            
return collection;
        }

      
      版本二:生成强类型委托。强类型委托改掉了DynamicInvoke带来的性能问题。

                说明:强类型委托我知道的有两种方式:

                     第一:Func<(T, TResult> 泛型委托,当然这种委托还包含一个参数以上的方式,但它需要有返回值。
                     第二:Action<T, 参数类型>,与Func不同的就是它并不需要返回值。所以下面我采用了Action方式。

public List<T> ExpressionTree<T>(List<T> collection, object propertyName, string propertyValue)
        {
            ParameterExpression parameter 
= Expression.Parameter(typeof(T), "x");

            ParameterExpression value 
= Expression.Parameter(typeof(string), "propertyValue");

            MethodInfo setter 
= typeof(T).GetMethod("set_" + propertyName);

            MethodCallExpression call 
= Expression.Call(parameter, setter, value);
            var lambda 
= Expression.Lambda<Action<T, string>>(call, parameter, value);
            var exp 
= lambda.Compile();
            
for (int i = 0; i < collection.Count; i++)
            {
                exp(collection[i], propertyValue);
            }
            
return collection;
        }

       
       版本二和反射方式的性能比较:先帖下相关代码
        1:实体类:  

public class Book
    {
        
public string sTitle
        { 
getset; }
        
public string sContent
        { 
getset; }
        
public int iID
        { 
getset; }
    }

      
       2:修改任务泛型实体集属性类:上面都有,就不重复了。
       3:构造一个包含一百万的实体集:    

private List<Book> GetData()
        {
            List
<Book> list = new List<Book>();
            
for (int i = 0; i < 1000000; i++)
            {
                Book b 
= new Book();
                b.sTitle 
= "标题" + i.ToString();
                b.sContent 
= "内容" + i.ToString();
                b.iID 
= i;
                list.Add(b);
            }
            
return list;
        }

      
       4:性能比较:
            

List<Book> list = this.GetData();
            SetAllPropertyMethod
<Book> s = new SetAllPropertyMethod<Book>();         
            Stopwatch sw 
= new Stopwatch();
            sw.Start();
            list 
= s.SetAllProperty(list, "sTitle""1");
            sw.Stop();
            
double time1 = sw.ElapsedMilliseconds;

            Response.Write(
"反射方式用时:"+time1 .ToString ());

            sw 
= new Stopwatch();
            sw.Start();
            list 
= s.ExpressionTree<Book>(list, "sTitle""1");
            sw.Stop();
            time1 
= sw.ElapsedMilliseconds;
            Response.Write(
"表达式树用时:" + time1.ToString());

      
       5:输出结果如下:反射方式用时:3198表达式树用时:38 ,可能看出接近100倍的性能优势。

            说话这个Stopwatch原来我也没有用过,汗,编码快四年了,居然这种好东西没用过。是不是有些朋友也和我一要只会利用TimeSpan来比较呢,哈哈。
            说明:这种表达式树只生成一次,即可完成整个实体集的应用,起到了非常好的复用作用。

       表达式树的优势:
           表达式树拥有语义清晰,强类型等优势,表达式树的计算对于性能的影响会越来越大,由于减少了编译操作和反射操作的次数,计算所需开销大大降低。

posted on 2009-09-27 19:51  min.jiang  阅读(4501)  评论(9编辑  收藏  举报