zoukankan      html  css  js  c++  java
  • C#中的表达式树

        本人之前从未接触过表达式树的概念,所以特意从网上找到两篇这方面的资料学习了下。本文为阅读笔记性质博客!

        表达式树是.NET 3.5之后引入的,它是一个强大灵活的工具(比如用在LINQ中构造动态查询)。

        先来看看Expression类的API接口:

    using System.Collections.ObjectModel;
     
    namespace System.Linq.Expressions
    {
        // Summary:
        //     Represents a strongly typed lambda expression as a data structure in the
        //     form of an expression tree. This class cannot be inherited.
        //
        // Type parameters:
        //   TDelegate:
        //     The type of the delegate that the System.Linq.Expressions.Expression<tdelegate>
        //     represents.
        public sealed class Expression<tdelegate> : LambdaExpression
        {
            // Summary:
            //     Compiles the lambda expression described by the expression tree into executable
            //     code.
            //
            // Returns:
            //     A delegate of type TDelegate that represents the lambda expression described
            //     by the System.Linq.Expressions.Expression<tdelegate>.
            public TDelegate Compile();
        }
    }

        表达式树的语法如下:

    Expression<Func<type,returnType>> = (param) => lamdaexpresion;

        我们先来看一个简单例子:

    Expression<Func<int, int, int>> expr = (x, y) => x+y;

        这就是一个表达式树了。使用Expression Tree Visualizer工具(直接调试模式下看也可以,只不过没这个直观)在调试模式下查看这个表达式树(就是一个对象),如下:

    exp_tree

        可以看到表达式树主要由下面四部分组成:

    1、Body 主体部分
    2、Parameters 参数部分
    3、NodeType 节点类型
    4、Lambda表达式类型

        对于前面举的例子,主体部分即x+y,参数部分即(x,y)。Lambda表达式类型是Func<Int32, Int32, Int32>。注意主体部分可以是表达式,但是不能包含语句,如下这样:

    Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };
         会报编译错误“Lambada expression with state body cannot be converted to expression tree”:即带有语句的Lambda表达式不能转换成表达式树。

        用前面的方法虽然可以创建表达式树,但是不够灵活,如果要灵活构建表达式树,可以像下面这样:

    ParameterExpression exp1 = Expression.Parameter(typeof(int), "a");
    ParameterExpression exp2 = Expression.Parameter(typeof(int), "b");
    
    BinaryExpression exp = Expression.Multiply(exp1,exp2);
    var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });

        exp1、exp2即表达式树的参数,exp是表达式树的主体。如果我利用Reflector反编译Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };得到下面的C#代码:

    ParameterExpression CS$0$0000;
    ParameterExpression CS$0$0001;
    Expression<Func<int, int, int>> expr = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(CS$0$0000 = Expression.Parameter(typeof(int), "x"), CS$0$0001 = Expression.Parameter(typeof(int), "y")), new ParameterExpression[] { CS$0$0000, CS$0$0001 });

        可以看到它基本和上面的手动构建代码一致。再来看一个简单的例子:

    Expression<Func<Customer, bool>> filter =
        cust => Equal(Property(cust,"Region"),"North");

        可以用下面的代码手动构建效果等同于上面的表达式树:

    // declare a parameter of type Customer named cust
    
    ParameterExpression custParam = Expression.Parameter(
    
        typeof(Customer), "custParam");
    
    // compare (equality) the Region property of the
    
    // parameter against the string constant "North"
    
    BinaryExpression body = Expression.Equal(
    
        Expression.Property(custParam, "Region"),
    
        Expression.Constant("North", typeof(string)));
    
    // formalise this as a lambda
    
    Expression<Func<Customer, bool>> filter =
    
        Expression.Lambda<Func<Customer, bool>>(body, cust);

        然后我们可以通过表达式树的Compile方法将表达式树编译成Lambda表达式,如下:

    Func<Customer, bool> filterFunc = filter.Compile();
        

        但是Compile调用过程涉及动态代码生成,所以出于性能考虑最好只调用一次,然后缓存起来。或者像下面这样在静态构造块中使用(也只会调用一次):

    public static class Operator<T>
    {
        private static readonly Func<T, T, T> add;
        public static T Add(T x, T y)
        {
            return add(x, y);
        }
        static Operator()
        {
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");
            var body = Expression.Add(x, y);
            add = Expression.Lambda<Func<T, T, T>>(
                body, x, y).Compile();
        }
    }
        

        Expression类包含下面几类静态方法(.NET 3.5中):

    Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, 
    Subtract, SubtractChecked, UnaryPlus
    
    Creation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
    
    Bitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)
    
    Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, 
    LessThanOrEqual, NotEqual, OrElse (||), TypeIs
    
    Member Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField
    
    Other: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote

        下面我们类似前面重载一个浅拷贝的例子(比使用反射开销小):

    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace ExpressionTreeLab
    {
        class Program
        {
            static void Main(string[] args)
            {
                var p = new Person()
                    {
                        Name = "jxq",
                        Age = 23
                    };
                var shallowCopy = Operator<Person>.ShallowCopy(p);
                shallowCopy.Name = "feichexia";
                Console.WriteLine(shallowCopy.Name);
                Console.WriteLine(p.Name);
    
                Console.ReadKey();
            }
    
            public class Person
            {
                public string Name { get; set; }
                public int Age { get; set; }
            }
    
            public static class Operator<T>
            {
                private static readonly Func<T, T> ShallowClone; 
    
                public static T ShallowCopy(T sourcObj)
                {
                    return ShallowClone.Invoke(sourcObj);
                }
    
                static Operator()
                {
                    var origParam = Expression.Parameter(typeof(T), "orig");
    
                    // for each read/write property on T, create a  new binding 
                    // (for the object initializer) that copies the original's value into the new object 
                    var setProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                            where prop.CanRead && prop.CanWrite
                                            select (MemberBinding)Expression.Bind(prop, Expression.Property(origParam, prop));
    
                    var body = Expression.MemberInit( // object initializer 
                        Expression.New(typeof(T)), // ctor 
                        setProps // property assignments 
                    );
    
                    ShallowClone = Expression.Lambda<Func<T, T>>(body, origParam).Compile();
                }
            }
        }
    }

        继续看Expression.AndAlso的使用,它可以用来替代类似下面这种多条件与的情况:

    Func<Person, Person, bool> personEqual = (person1, person2) => person1.Name == person2.Name && person1.Age == person2.Age;
     if(personEqual(p1, p2))
    {
        Console.WriteLine("两个对象所有属性值都相等!");
    }

        代码如下:

    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace ExpressionTreeLab
    {
        class Program
        {
            static void Main(string[] args)
            {
                var p1 = new Person()
                    {
                        Name = "jxq",
                        Age = 23
                    };
                var p2 = new Person()
                    {
                        Name = "jxq",
                        Age = 23
                    };
    
                if (Operator<Person>.ObjectPropertyEqual(p1, p2))
                {
                    Console.WriteLine("两个对象所有属性值都相等!");
                }
                
                Console.ReadKey();
            }
    
            public class Person
            {
                public string Name { get; set; }
                public int Age { get; set; }
            }
    
            public static class Operator<T>
            {
                private static readonly Func<T, T, bool> PropsEqual; 
    
                public static bool ObjectPropertyEqual(T obj1, T obj2)
                {
                    return PropsEqual.Invoke(obj1, obj2);
                }
    
                static Operator()
                {
                    var x = Expression.Parameter(typeof(T), "x");
                    var y = Expression.Parameter(typeof(T), "y");
    
                    // 获取类型T上的可读Property
                    var readableProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                            where prop.CanRead
                                            select prop;
    
                    Expression combination = null;
                    foreach (var readableProp in readableProps)
                    {
                        var thisPropEqual = Expression.Equal(Expression.Property(x, readableProp),
                                                             Expression.Property(y, readableProp));
    
                        if(combination == null)
                        {
                            combination = thisPropEqual;
                        }
                        else
                        {
                            combination = Expression.AndAlso(combination, thisPropEqual);
                        }
                    }
    
                    if(combination == null)   // 如果没有需要比较的东西,直接返回false
                    {
                        PropsEqual = (p1, p2) => false;
                    }
                    else
                    {
                        PropsEqual = Expression.Lambda<Func<T, T, bool>>(combination, x, y).Compile();
                    }
                }
            }
        }
    }

        在.NET 4.0中扩展了一些Expression的静态方法,使得编写动态代码更容易:

    Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked
    
    Arithmetic: Decrement, Default, Increment, OnesComplement
    
    Member Access: ArrayAccess, Dynamic
    
    Logical: ReferenceEqual, ReferenceNotEqual, TypeEqual
    
    Flow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable
    
    Exceptions: Catch, Rethrow, Throw
    
    Debug: ClearDebugInfo, DebugInfo

        下面是一个利用表达式树编写动态代码的例子(循环打印0到9):

    using System;
    using System.Linq.Expressions;
    
    namespace ExpressionTreeLab
    {
        class Program
        {
            static void Main(string[] args)
            {
                var exitFor = Expression.Label("exitFor"); // jump label
                var x = Expression.Variable(typeof(int), "x");
                var body = 
                    Expression.Block(
                        new[] { x }, // declare scope variables
                        Expression.Assign(x, Expression.Constant(0, typeof(int))), // init
                        Expression.Loop(
                            Expression.IfThenElse(
                                Expression.GreaterThanOrEqual( // test for exit
                                    x,
                                    Expression.Constant(10, typeof(int))
                                ),
                                Expression.Break(exitFor), // perform exit
                                Expression.Block( // perform code
                                    Expression.Call(
                                        typeof(Console), "WriteLine", null, x),
                                    Expression.PostIncrementAssign(x)
                                )
                            ), exitFor
                         )  // Loop ends
                     );
    
                var runtimeLoop = Expression.Lambda<Action>(body).Compile();
                runtimeLoop();
    
                Console.Read();
            }
    
        }
    }

        另外WhereIn扩展实现如下,如果前面的例子都熟悉了的话,这个自然也很容易看懂了:

        /// <summary>
        ///   使之支持Sql in语法
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <typeparam name = "TValue"></typeparam>
        /// <param name = "query"></param>
        /// <param name = "obj"></param>
        /// <param name = "values"></param>
        /// <returns></returns>
        public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values)
        {
            return query.Where(BuildContainsExpression(obj, values));
        }
    
        private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
            Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector)
            {
                throw new ArgumentNullException("valueSelector");
            }
            if (null == values)
            {
                throw new ArgumentNullException("values");
            }
            var p = valueSelector.Parameters.Single();
            if (!values.Any()) return e => false;
    
            var equals = values.Select(value => (Expression) Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof (TValue))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

        调用方式如下:

    db.Users.WhereIIn(u => u.Id, new int[] { 1, 2, 3 });

        关于使用表达式树构建LINQ动态查询,请参考Dynamic Linq Queries with Expression Trees

    参考资料:

    http://www.codeproject.com/Tips/438804/Expression-Tree

    http://www.infoq.com/articles/expression-compiler

  • 相关阅读:
    ll command not found 当ll无法识别的解决办法
    idea控制台全屏
    查看centos版本号
    java Error: 无法访问org.apache.http.annotation.ThreadSafe 找不到org.apache.http.annotation.ThreadSafe的类文件
    DigestUtils.md5Hex()加密
    JAVA 8 '::' 关键字
    CVE-2020-1472 NetLogon特权提升漏洞
    OpenSSH的scp命令注入漏洞(CVE-2020-15778)
    redis未授权访问漏洞&简单利用&总结
    常见web信息泄露
  • 原文地址:https://www.cnblogs.com/feichexia/p/3104832.html
Copyright © 2011-2022 走看看