好久没更新这个系列了,最近看.NET CORE源码的时候,发现他的依赖注入模块的很多地方用了表达式拼接实现的。比如如下代码
private Expression<Func<ServiceProviderEngineScope, object>> BuildExpression(IServiceCallSite callSite) { var context = new CallSiteExpressionBuilderContext { ScopeParameter = ScopeParameter }; var serviceExpression = VisitCallSite(callSite, context); if (context.RequiresResolvedServices) { return Expression.Lambda<Func<ServiceProviderEngineScope, object>>( Expression.Block( new [] { ResolvedServices }, ResolvedServicesVariableAssignment, Lock(serviceExpression, ResolvedServices)), ScopeParameter); } return Expression.Lambda<Func<ServiceProviderEngineScope, object>>(serviceExpression, ScopeParameter); }
所以今天我们先一起了解下表达式树以及它的一种实用应用——表达式树进行类的快速赋值。
提示:学习这一章,需要有一定拉姆达基础,如果不太了解拉姆达,推荐阅读《C#进阶之路(四):拉姆达》。
一、初识表达式树
表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。LINQ to SQL就是通过把表达式树翻译成SQL来实现的,所以了解表达树有助于我们更好的理解 LINQ to SQL,同时如果你有兴趣,可以用它创造出很多有意思的东西来。
根据Lambda表达式来创建表达式树,这应该是最直接的创建表达式树的方式了。
Expression<Func<int, int>> expr = x => x + 1; Console.WriteLine(expr.ToString()); // x=> (x + 1) // 下面的代码编译不通过 Expression<Func<int, int, int>> expr2 = (x, y) => { return x + y; }; Expression<Action<int>> expr3 = x => { };
这种方式只能创建最简单的表达式树,复杂点的编译器就不认识了。
右边是一个Lambda表达式,而左边是一个表达式树。为什么可以直接赋值呢?这个就要多亏我们的Expression<TDelegate>泛型类了。而Expression<TDelegate>是直接继承自LambdaExpression的,我们来看一下Expression的构造函数:
internal Expression(Expression body, string name, bool tailCall,ReadOnlyCollection<ParameterExpression> parameters) : base(typeof(TDelegate), name, body, tailCall, parameters) { }
实际上这个构造函数什么也没有做,只是把相关的参数传给了父类,也就是LambdaExpression,由它把我们表达式的主体,名称,以及参数保存着。
Expression<Func<int, int>> expr = x => x + 1; Console.WriteLine(expr.ToString()); // x=> (x + 1) var lambdaExpr = expr as LambdaExpression; Console.WriteLine(lambdaExpr.Body); // (x + 1) Console.WriteLine(lambdaExpr.ReturnType.ToString()); // System.Int32 foreach (var parameter in lambdaExpr.Parameters) { Console.WriteLine("Name:{0}, Type:{1}, ",parameter.Name,parameter.Type.ToString()); } //Name:x, Type:System.Int32
二、创建一个复杂的Lambda表达式树
上面我们讲到直接由Lambda表达式的方式来创建表达式树,可惜只限于一种类型。下面我们就来演示一下如何创建一个无参无返回值的表达式树。
// 下面的方法编译不能过 /* Expression<Action> lambdaExpression2 = () => { for (int i = 1; i <= 10; i++) { Console.WriteLine("Hello"); } }; */ // 创建 loop表达式体来包含我们想要执行的代码 LoopExpression loop = Expression.Loop( Expression.Call( null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Hello")) ); // 创建一个代码块表达式包含我们上面创建的loop表达式 BlockExpression block = Expression.Block(loop); // 将我们上面的代码块表达式 Expression<Action> lambdaExpression = Expression.Lambda<Action>(block); lambdaExpression.Compile().Invoke();
上面我们通过手动编码的方式创建了一个无参的Action,执行了一组循环。代码很简单,重要的是我们要熟悉这些各种类型的表达式以及他们的使用方式。上面我们引入了以下类型的表达式:
看起来神密的表达式树也不过如此嘛?如果大家去执行上面的代码,就会陷入死循环,我没有为loop加入break的条件。为了方便大家理解,我是真的一步一步来啊,现在我们就来终止这个循环。就像上面那一段不能编译通过的代码实现的功能一样,我们要输出10个”Hello”。
上面我们先写了一个LoopExpression,然后把它传给了BlockExpresson,从而形成的的一块代码或者我们也可以说一个方法体。但是如果我们有多个执行块,而且这多个执行块里面需要处理同一个参数,我们就得在block里面声明这些参数了。
ParameterExpression number=Expression.Parameter(typeof(int),"number"); BlockExpression myBlock = Expression.Block( new[] { number }, Expression.Assign(number, Expression.Constant(2)), Expression.AddAssign(number, Expression.Constant(6)), Expression.DivideAssign(number, Expression.Constant(2))); Expression<Func<int>> myAction = Expression.Lambda<Func<int>>(myBlock); Console.WriteLine(myAction.Compile()()); // 4
我们声明了一个int的变量并赋值为2,然后加上6最后除以2。如果我们要用变量,就必须在block的你外面声明它,并且在block里面把它引入进来。否则在该表达式树时会出现,变量不在作用域里的错。
下面我们继续我们未完成的工作,为循环加入退出条件。为了让大家快速的理解loop的退出机制,我们先来看一段伪代码:
LabelTarget labelBreak = Expression.Label(); Expression.Loop( "如果 条件 成功" "执行成功的代码" "否则" Expression.Break(labelBreak) //跳出循环 , labelBreak);
我们需要借助于LabelTarget 以及Expression.Break来达到退出循环的目地。下面我们来看一下真实的代码:
LabelTarget labelBreak = Expression.Label(); ParameterExpression loopIndex = Expression.Parameter(typeof(int), "index"); BlockExpression block = Expression.Block( new[] { loopIndex }, // 初始化loopIndex =1 Expression.Assign(loopIndex, Expression.Constant(1)), Expression.Loop( Expression.IfThenElse( // if 的判断逻辑 Expression.LessThanOrEqual(loopIndex, Expression.Constant(10)), // 判断逻辑通过的代码 Expression.Block( Expression.Call( null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Hello")), Expression.PostIncrementAssign(loopIndex)), // 判断不通过的代码 Expression.Break(labelBreak) ),labelBreak)); // 将我们上面的代码块表达式 Expression<Action> lambdaExpression = Expression.Lambda<Action>(block); lambdaExpression.Compile().Invoke();
好吧,我们又学了几个新的类型的表达式,来总结一下:
到这里,我想大家应该对表达式树的构建有了一个清楚的认识。至于为什么不允许我们直接基于复杂的Lambda表达式来创建表达式树呢?
这里的Lambda表达式实际上是一个Expression Body。
这个Expression Body实际上就是我们上面讲到的Expression中的一种。
也就是说编译器需要时间去分析你到底是哪一种?
最简单的x=> x+1之类的也就是Func<TValue,TKey> 是很容易分析的。
实际这里面允许的Expression Body只有BinaryExpression。
最后,我们来完整的看一下.NET都为我们提供了哪些类型的表达式(下面这些类都是继承自Expression)。
TypeBinaryExpression TypeBinaryExpression typeBinaryExpression = Expression.TypeIs( Expression.Constant("spruce"), typeof(int)); Console.WriteLine(typeBinaryExpression.ToString()); // ("spruce" Is Int32) IndexExpression ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array"); ParameterExpression indexExpr = Expression.Parameter(typeof(int), "Index"); ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value"); Expression arrayAccessExpr = Expression.ArrayAccess( arrayExpr, indexExpr ); Expression<Func<int[], int, int, int>> lambdaExpr = Expression.Lambda<Func<int[], int, int, int>>( Expression.Assign(arrayAccessExpr, Expression.Add(arrayAccessExpr, valueExpr)), arrayExpr, indexExpr, valueExpr ); Console.WriteLine(arrayAccessExpr.ToString()); // Array[Index] Console.WriteLine(lambdaExpr.ToString()); // (Array, Index, Value) => (Array[Index] = (Array[Index] + Value)) Console.WriteLine(lambdaExpr.Compile().Invoke(new int[] { 10, 20, 30 }, 0, 5)); // 15 NewExpression NewExpression newDictionaryExpression =Expression.New(typeof(Dictionary<int, string>)); Console.WriteLine(newDictionaryExpression.ToString()); // new Dictionary`2() InvocationExpression Expression<Func<int, int, bool>> largeSumTest = (num1, num2) => (num1 + num2) > 1000; InvocationExpression invocationExpression= Expression.Invoke( largeSumTest, Expression.Constant(539), Expression.Constant(281)); Console.WriteLine(invocationExpression.ToString()); // Invoke((num1, num2) => ((num1 + num2) > 1000),539,281)