C# 表达式树 Expression Trees知识总结


C# 知识回顾 - 表达式树 Expression Trees

目录

  • 简介
  • Lambda 表达式创建表达式树
  • API 创建表达式树
  • 解析表达式树
  • 表达式树的永久性
  • 编译表达式树
  • 执行表达式树
  • 修改表达式树
  • 调试

Expression 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。  

  C# 编译器只能从表达式 lambda (或单行 lambda)生成表达式树。 

  下列代码示例使用关键字 Expression创建表示 lambda 表达式:

1             Expressionint>> actionExpression = n => Console.WriteLine(n);
2             Expressionint, bool>> funcExpression1 = (n) => n < 0;
3             Expressionint, int, bool>> funcExpression2 = (n, m) => n - m == 0;

Expression 类

  下列代码示例展示如何通过 API 创建表示 lambda 表达式:num => num == 0

1             //通过 Expression 类创建表达式树
2             //  lambda:num => num == 0
3             ParameterExpression pExpression = Expression.Parameter(typeof(int));    //参数:num
4             ConstantExpression cExpression = Expression.Constant(0);    //常量:0
5             BinaryExpression bExpression = Expression.MakeBinary(ExpressionType.Equal, pExpression, cExpression);   //表达式:num == 0
6             Expressionint, bool>> lambda = Expression.Lambdaint, bool>>(bExpression, pExpression);  //lambda 表达式:num => num == 0

  代码使用 Expression 类的静态方法进行创建。

Expression 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。

1             //创建表达式树
2             Expressionstring, int>> funcExpression = msg => msg.Length;
3             //表达式树编译成委托
4             var lambda = funcExpression.Compile();
5             //调用委托
6             Console.WriteLine(lambda("Hello, World!"));
7 
8             //语法简化
9             Console.WriteLine(funcExpression.Compile()("Hello, World!"));

LambdaExpression 或 Expression 类型。若要执行这些表达式树,需要调用 Compile 方法来创建一个可执行委托,然后调用该委托。
 1             const int n = 1;
 2             const int m = 2;
 3 
 4             //待执行的表达式树
 5             BinaryExpression bExpression = Expression.Add(Expression.Constant(n), Expression.Constant(m));
 6             //创建 lambda 表达式
 7             Expressionint>> funcExpression = Expression.Lambdaint>>(bExpression);
 8             //编译 lambda 表达式
 9             Func<int> func = funcExpression.Compile();
10 
11             //执行 lambda 表达式
12             Console.WriteLine($"{n} + {m} = {func()}");

ExpressionVisitor 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。  

 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             Expressionint, bool>> funcExpression = num => num == 0;
 6             Console.WriteLine($"Source: {funcExpression}");
 7 
 8             var visitor = new NotEqualExpressionVisitor();
 9             var expression = visitor.Visit(funcExpression);
10 
11             Console.WriteLine($"Modify: {expression}");
12 
13             Console.Read();
14         }
15 
16         /// 
17         /// 不等表达式树访问器
18         /// 
19         public class NotEqualExpressionVisitor : ExpressionVisitor
20         {
21             public Expression Visit(BinaryExpression node)
22             {
23                 return VisitBinary(node);
24             }
25 
26             protected override Expression VisitBinary(BinaryExpression node)
27             {
28                 return node.NodeType == ExpressionType.Equal
29                     ? Expression.MakeBinary(ExpressionType.NotEqual, node.Left, node.Right) //重新弄个表达式:用 != 代替 ==
30                     : base.VisitBinary(node);
31             }
32         }
33     }

?
1 Expressionint, int, int, int>> expr = (x, y, z) => (x + y) / z;

2.编译表达式树,Expression<TDelegate> type provides the Compile method that compiles the code represented by an expression tree into an executable delegate. ">该方法将表达式树表示的代码编译成一个可执行委托

?
1 expr.Compile()(1, 2, 3)

3.IQueryable的扩展方法,WhereIn的实现

?
1 var d = list.AsQueryable().WhereIn(o => o.Id1, new int[] { 1, 2 });

 完整代码:

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace MongoDBTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //使用LambdaExpression构建表达式树
            Expressionint, int, int, int>> expr = (x, y, z) => (x + y) / z;
            Console.WriteLine(expr.Compile()(1, 2, 3));

            //使用LambdaExpression构建可执行的代码
            Func<int, int, int, int> fun = (x, y, z) => (x + y) / z;
            Console.WriteLine(fun(1, 2, 3));

            //动态构建表达式树
            ParameterExpression pe1 = Expression.Parameter(typeof(int), "x");
            ParameterExpression pe2 = Expression.Parameter(typeof(int), "y");
            ParameterExpression pe3 = Expression.Parameter(typeof(int), "z");
            var body = Expression.Divide(Expression.Add(pe1, pe2), pe3);
            var w = Expression.Lambdaint, int, int, int>>(body, new ParameterExpression[] { pe1, pe2, pe3 });
            Console.WriteLine(w.Compile()(1, 2, 3));

            List list = new List { new Entity { Id1 = 1 }, new Entity { Id1 = 2 }, new Entity { Id1 = 3 } };

            var d = list.AsQueryable().WhereIn(o => o.Id1, new int[] { 1, 2 });
            d.ToList().ForEach(o =>
            {
                Console.WriteLine(o.Id1);
            });

            Console.ReadKey();

        }


    }
    public class Entity
    {
        public ObjectId Id;
        public int Id1;
        public string Name { get; set; }
    }

    public static class cc
    {


        public static IQueryable WhereIn(this IQueryable query, Expression> obj, IEnumerable values)
        {
            return query.Where(BuildContainsExpression(obj, values));
        }

        private static Expressionbool>> BuildContainsExpression(Expression> valueSelector, IEnumerable 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.Lambdabool>>(body, p);
        }
    }
}

 参考博客:

  1. C#中的表达式树
  2. 表达式树基础
  3. C#高级程序设计(九)——表达式树
  4. 表达式树MSDN

出处:https://www.cnblogs.com/LittleFeiHu/p/4050428.html

=======================================================================================

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
    //     represents.
    public sealed class Expression : 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.
        public TDelegate Compile();
    }
}

    表达式树的语法如下:

Expression> = (param) => lamdaexpresion;

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

Expressionint, int, int>> expr = (x, y) => x+y;

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

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

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

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

Expressionint, 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.Lambdaint, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });

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

ParameterExpression CS$0$0000;
ParameterExpression CS$0$0001;
Expressionint, int, int>> expr = Expression.Lambdaint, 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 });

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

Expressionbool>> 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

Expressionbool>> filter =

    Expression.Lambdabool>>(body, cust);

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

Funcbool> filterFunc = filter.Compile();
    

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

public static class Operator
{
    private static readonly Func 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>(
            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.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
        {
            private static readonly Func 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>(body, origParam).Compile();
            }
        }
    }
}

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

Funcbool> 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.ObjectPropertyEqual(p1, p2))
            {
                Console.WriteLine("两个对象所有属性值都相等!");
            }
            
            Console.ReadKey();
        }

        public class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }

        public static class Operator
        {
            private static readonly Funcbool> 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.Lambdabool>>(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(body).Compile();
            runtimeLoop();

            Console.Read();
        }

    }
}

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

    /// 
    ///   使之支持Sql in语法
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public static IQueryable WhereIn(this IQueryable query, Expression> obj, IEnumerable values)
    {
        return query.Where(BuildContainsExpression(obj, values));
    }

    private static Expressionbool>> BuildContainsExpression(
        Expression> valueSelector, IEnumerable 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.Lambdabool>>(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

出处:https://www.cnblogs.com/feichexia/archive/2013/05/28/3104832.html

C