表达式树对性能的影响

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

      原题:日常应用中我们常常会和一些数据实体类打交道。现在假设我们的数据实体类里面定义了许多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
        { 
get set ; }
        
public   string  sContent
        { 
get set ; }
        
public   int  iID
        { 
get set ; }
    }

      
       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来比较呢,哈哈。
            说明:这种表达式树只生成一次,即可完成整个实体集的应用,起到了非常好的复用作用。

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

转载于:https://www.cnblogs.com/ASPNET2008/archive/2009/09/27/1575198.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java表达式是一种数据结构,它用于表示数学表达式,并且可以方便地计算这些表达式。下面是一个简单的Java程序,用于计算一个包含加、减、乘、除四种运算符的表达式: ```java import java.util.Stack; public class ExpressionTree { private Node root; private class Node { public String value; public Node left, right; public Node(String value) { this.value = value; this.left = null; this.right = null; } } public ExpressionTree(String expression) { Stack<Node> stack = new Stack<Node>(); String[] tokens = expression.split(" "); for (String token : tokens) { if (isOperator(token)) { Node right = stack.pop(); Node left = stack.pop(); Node node = new Node(token); node.left = left; node.right = right; stack.push(node); } else { Node node = new Node(token); stack.push(node); } } root = stack.pop(); } public double evaluate() { return evaluate(root); } private double evaluate(Node node) { if (node == null) return 0; if (node.left == null && node.right == null) return Double.parseDouble(node.value); double left = evaluate(node.left); double right = evaluate(node.right); switch (node.value) { case "+": return left + right; case "-": return left - right; case "*": return left * right; case "/": return left / right; default: return 0; } } private boolean isOperator(String token) { return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/"); } public static void main(String[] args) { ExpressionTree tree = new ExpressionTree("5 1 2 + 4 * + 3 -"); System.out.println(tree.evaluate()); } } ``` 这个程序可以计算任意一个包含加、减、乘、除四种运算符的表达式,并且输出计算结果。例如,上面的main方法会输出16.0,因为这个表达式的计算结果是5 + (1 + 2) * 4 - 3 = 16. ### 回答2: Java表达式计算是一种将表达式转换为形结构并进行计算的方法。首先,通过解析表达式,可以将其转换为型结构,该结构由节点和连接它们的边组成。节点可以是操作符或操作数,并且每个操作符节点都有子节点,表示它的操作数。 在计算表达式时,我们可以使用递归的方法遍历整个。从根节点开始,我们检查当前节点的类型是操作符还是操作数。如果是操作数节点,则直接返回其值。如果是操作符节点,则递归计算其子节点的值,并根据操作符的类型对子节点的值进行相应的操作。 例如,假设我们有一个表达式的根节点是一个"+"节点,其左子节点是一个数字节点2,右子节点是一个数字节点3。在计算表达式时,我们首先递归计算左子节点的值,得到2,然后递归计算右子节点的值,得到3。最后,根据操作符节点的类型执行加法操作,并将结果返回。 通过递归遍历整个表达式,我们可以计算任意复杂度的表达式。这种方法的优势是可以对表达式进行灵活的解析和计算,可以处理各种类型的操作符和操作数,并且能够处理嵌套和多层级的表达式。 总而言之,Java表达式计算是一种将表达式转换为形结构并进行计算的方法,通过递归遍历整个表达式,可以计算任意复杂度的表达式。这种方法具有灵活性和扩展性,可以处理各种类型的操作符和操作数,并且能够处理嵌套和多层级的表达式。 ### 回答3: Java表达式计算是一种将数学表达式转化为二叉表示,并以结构进行计算的方法。该方法可以有效地解决复杂的数学运算问题。 在表达式中,每个节点都代表着一个操作符或者操作数。操作符节点有左子节点和右子节点,而操作数节点没有子节点。通过将数学表达式构建为表达式,我们可以很方便地对表达式进行计算。 计算表达式的过程,通常是通过递归算法实现的。首先,我们从根节点开始遍历整个,如果当前节点是操作数节点,直接返回该节点的值。如果当前节点是操作符节点,则需要对其左子和右子进行递归计算,并根据操作符节点的运算规则对子结果进行运算,得到最终的计算结果。 通过表达式计算,我们可以实现对复杂的数学表达式的计算,包括加减乘除以及各种嵌套运算的组合。这种方法可以提高计算效率,减少重复计算的次数,提升程序性能。 总之,Java表达式计算是一种将数学表达式转化为结构,并通过递归算法对进行计算的方法。它可以方便地解决复杂的数学运算问题,提高程序的计算效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值