expression tree 表达式目录树是.NET 3.5引入的新增功能,简单的说是一种语法树,或者说是一种数据结构(Expression)。表达式树可以说是Linq的核心之一,为什么是Linq的核心之一呢?因为表达式树使得c#不再是仅仅能编译成IL,我们可以通过c#生成一个表达式树,将结果作为一个中间格式,在将其转换成目标平台上的本机语言。比如SQL。我们常用的Linq to sql就是这样生成SQL语句。
在.net框架重定义的expression
using System.Collections.Generic; using System.Runtime.CompilerServices; namespace System.Linq.Expressions { // // 摘要: // 将强类型化的 Lambda 表达式表示为表达式树形式的数据结构。 此类不能被继承。 // // 类型参数: // TDelegate: // 该委托的类型, System.Linq.Expressions.Expression`1 表示。 public sealed class Expression<TDelegate> : LambdaExpression { // // 摘要: // 编译表达式树由描述为可执行代码的 lambda 表达式,并生成一个委托,表示 lambda 表达式。 // // 返回结果: // 类型的委托 TDelegate 表示所描述的已编译的 lambda 表达式 System.Linq.Expressions.Expression`1。 public TDelegate Compile(); // // 摘要: // 将生成一个委托,表示 lambda 表达式。 // // 参数: // debugInfoGenerator: // 由编译器用于将标记序列点并添加批注的本地变量的调试信息生成器。 // // 返回结果: // 一个包含已编译的版本的 lambda 的委托。 public TDelegate Compile(DebugInfoGenerator debugInfoGenerator); // // 摘要: // 创建一个新的表达式,它类似于此表达式,但使用所提供的子级。 如果所有子级均相同,它将返回此表达式。 // // 参数: // body: // System.Linq.Expressions.LambdaExpression.Body 结果属性。 // // parameters: // System.Linq.Expressions.LambdaExpression.Parameters 结果属性。 // // 返回结果: // 如果子级未更改,则为此表达式;否则为具有已更新子级的表达式。 public Expression<TDelegate> Update(Expression body, IEnumerable<ParameterExpression> parameters); protected internal override Expression Accept(ExpressionVisitor visitor); } }
由此可以看出,Expression<TDelegate>在本质上是一个密封的泛型类,我们可以通过给Expression<TDelegate>赋值一个lambel表达式的方式类得到一个表达式目录树。
我们再进一步分析一下,Expression<TDelegate>泛型类继承于LambdaExpression,是一个抽象类,
using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection.Emit; using System.Runtime.CompilerServices; namespace System.Linq.Expressions { // // 摘要: // 介绍 lambda 表达式。 它捕获一个类似于 .NET 方法主体的代码块。 [DebuggerTypeProxy(typeof(LambdaExpressionProxy))] public abstract class LambdaExpression : Expression { // // 摘要: // 获取此 System.Linq.Expressions.Expression 表示的表达式的静态类型。 // // 返回结果: // 表示表达式的静态类型的 System.Linq.Expressions.LambdaExpression.Type。 public sealed override Type Type { get; } // // 摘要: // 返回此节点类型 System.Linq.Expressions.Expression。 // // 返回结果: // 用于表示此表达式的 System.Linq.Expressions.ExpressionType。 public sealed override ExpressionType NodeType { get; } // // 摘要: // 获取 lambda 表达式的参数。 // // 返回结果: // 一个 System.Collections.ObjectModel.ReadOnlyCollection`1 的 System.Linq.Expressions.ParameterExpression // 对象,表示 lambda 表达式的参数。 public ReadOnlyCollection<ParameterExpression> Parameters { get; } // // 摘要: // 获取 lambda 表达式的名称。 // // 返回结果: // Lambda 表达式的名称。 public string Name { get; } // // 摘要: // 获取 lambda 表达式的主体。 // // 返回结果: // System.Linq.Expressions.Expression 表示 lambda 表达式的主体。 public Expression Body { get; } // // 摘要: // 获取 lambda 表达式的返回类型。 // // 返回结果: // System.Type 对象,表示 lambda 表达式的类型。 public Type ReturnType { get; } // // 摘要: // 获取一个值,指示是否将使用尾调用优化编译 lambda 表达式。 // // 返回结果: // 如果 lambda 表达式将编译用尾调用优化,否则为 false,则为 true。 public bool TailCall { get; } // // 摘要: // 将生成一个委托,表示 lambda 表达式。 // // 返回结果: // 一个 System.Delegate ,其中包含 lambda 表达式的已编译的版本。 public Delegate Compile(); // // 摘要: // 将生成一个委托,表示 lambda 表达式。 // // 参数: // debugInfoGenerator: // 由编译器用于将标记序列点并添加批注的本地变量的调试信息生成器。 // // 返回结果: // 一个包含已编译的版本的 lambda 的委托。 public Delegate Compile(DebugInfoGenerator debugInfoGenerator); // // 摘要: // Lambda 将编译的方法定义。 // // 参数: // method: // 一个 System.Reflection.Emit.MethodBuilder 这将用于保存 lambda 的 IL。 public void CompileToMethod(MethodBuilder method); // // 摘要: // 将方法定义和自定义调试信息编译 lambda。 // // 参数: // method: // 一个 System.Reflection.Emit.MethodBuilder 这将用于保存 lambda 的 IL。 // // debugInfoGenerator: // 由编译器用于将标记序列点并添加批注的本地变量的调试信息生成器。 public void CompileToMethod(MethodBuilder method, DebugInfoGenerator debugInfoGenerator); } }
由上可以看出,表达式目录树就是一层一层的数据结构。
所有的表达式目录树都继承于Expression,下面是表达式目录树常用的类型
先来看看一组代码
Func<int, int, int> func = (m, n) => m * n + 2; Expression<Func<int, int, int>> exp = (m, n) => m * n + 2; int iResult1 = func.Invoke(99, 99); int iResult2 = exp.Compile().Invoke(99, 99);
iResult1 和iResult2的结果一样,但是能Compile()的只有LambdaExpression。 Compile() 是将表达式树描述的 Lambda 表达式编译为可执行代码,并生成表示该 lambda 表达式的委托。exp.Compile().Invoke(99,99) 相当于这样调用 exp.Compile()();
我们将上面的表达式目录树进行拆解一下,按照运算符的优先级,我们先计算m*n,再将相乘的结果加2,如下图。
如代码所示,m和n是参数,所以类型为ParameterExpression ,2是常量,常量类型是ConstantExpression ,MultiplyAssign 乘法,Add加法。第六步中只能执行表示Lambda表达式的表达式目录树,即LambdaExpression或者Expression<TDelegate>类型。如果表达式目录树不是表示Lambda表达式,需要调用Lambda方法创建一个新的表达式。exp.Compile()成委托,再调用。
我们再用表达式目录树的类型再次封装一下
{ ParameterExpression left = Expression.Parameter(typeof(int), "m");//左边的参数 ParameterExpression right = Expression.Parameter(typeof(int), "n");//右边的参数 ConstantExpression constantlExp = Expression.Constant(2, typeof(int));//常量2 BinaryExpression binaryExpMult = Expression.MultiplyAssign(left, right);//两个参数相乘 BinaryExpression binaryExpAdd = Expression.Add(binaryExpMult, constantlExp);//相乘的结果再加2 Expression<Func<int, int, int>> actExpression = Expression.Lambda<Func<int, int, int>>(binaryExpAdd, left, right); int result = actExpression.Compile()(2, 1);//调用 Console.WriteLine(result + ""); }
表达式树的意义:数据化的表达式
我们现在已经能够用两种方式创建表达式树——用Expression的节点组合或者直接从C#、VB的Lambda表达式生成。不管使用的是那种方法,最后我们得到的是一个内存中树状结构的数据。如果我们愿意,可以将它还原成文本源代码的表达式或者序列化到字符串里。注意,如果是C#的表达式本身,我们是没法对它进行输出或者序列化的,它只存在于编译之前的源文件里。现在的表达式树已经成为了一种数据,而不在是语言中的表达式。我们可以在运行的时候处理这一数据,精确了解其内在逻辑;将它传递给其他的程序或者再次编译成为可以执行的代码。这就可以总结为表达式树的三大基本用途:
- 运行时分析表达式的逻辑
- 序列化或者传输表达式
- 重新编译成可执行的代码
下面我们来实践一下。
在实际场景中,会有实体与实体之间的转化,如:People类转化成PeopleDto类。
先声明这两个类:
/// <summary> /// 实体类 /// </summary> public class People { public int Age { get; set; } public string Name { get; set; } public int Id; } /// <summary> /// 实体类Target /// </summary> public class PeopleDto { public int Age { get; set; } public string Name { get; set; } public int Id; }
我们先简单的实现一下
People people = new People() { Id = 11, Name = "Eleven", Age = 31 }; PeopleDto peopleCopy = new PeopleDto() { Id = people.Id, Name = people.Name, Age = people.Age };
功能是实现了,但是直接写死,以上代码只是对于People类实例people进行转化,但是对于其他实例呢?
我们再用表达式目录树实现一下:
Expression<Func<People, PeopleDto>> lambda = p =>//表达式目录树 new PeopleDto() { Id = p.Id, Name = p.Name, Age = p.Age }; lambda.Compile()(people);
虽然也实现了,但是任然写死了,他只是实现了people类的转化,但是下次换一种类型呢?如果换个Animal类呢?还是要封装一个方法。
其实最好的解决方案是表达目录树+泛型+缓存
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace ExpressionDemo.MappingExtend { /// <summary> /// 生成表达式目录树 缓存 /// </summary> public class ExpressionMapper { private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); /// <summary> /// 字典缓存表达式树 /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> /// <param name="tIn"></param> /// <returns></returns> public static TOut Trans<TIn, TOut>(TIn tIn) { string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName); if (!_Dic.ContainsKey(key)) { ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TOut).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } foreach (var item in typeof(TOut).GetFields()) { MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的 _Dic[key] = func; } return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn); } } }