现在的位置: 首页 > 综合 > 正文

将C# lambda表达式转换成表达式树

2013年09月07日 ⁄ 综合 ⁄ 共 2636字 ⁄ 字号 评论关闭

 

就像我们已经看到的, lambda表达式可以隐式或显式的被转换为适当的委托实例. 然而, 这并非唯一可用的转换规则, 你也可以让编译器帮你从一个lambda表达式来构建表达式树, 然后在执行时创建一个Expression<TDelegate>实例. 例如, 下面的例子使用了更简短的方式创建”return 5″的表达式, 然后编译并执行结果委托:

   1: Expression<Func<int>> return5 = () => 5;
   2: Func<int> compiled = return5.Compile();
   3: Console.WriteLine(compiled());

在第一行代码中, ()=>5是lambda表达式, 在这个例子中, 如果我们用圆括号将其包围会让其看起来更糟糕也不是更好. 注意我们并不需要任何的类型转换, 编译器可以帮助我们完成所有的东西. 你可以编写2+3代替5, 然而编译器会帮我们优化(编译后的代码直接保存的就是5). 很重要的一点就是, lambda表达式已经被转换为表达式树.

限制——并非所有的lambda表达式都可以被转换成为表达式树. 你不能将包含一整块语句(甚至是只有一个return语句)的表达式转换为表达式树——只能评估一个单一表达式. 表达式不能包含指派(assignments), 因为它们不能以表达式树的方式展现. 尽管这就是最常规的限制, 不过这并不是唯一——完全清单不值得再这里讨论, 因为这些问题极少碰到. 而且如果你试图尝试这种非法转换, 编译器会及时帮你发现的.

让我们再来看看一个更复杂的例子, 尤其是当引入参数的时候. 这次我们将编写一个断言来判断两个输入参数中, 第一个是否是可以从第二个参数开始的. 使用lambda表达式的依然还是很简单:

   1: Expression<Func<string,string,bool>> expression =
   2: ( (x,y) => x.StartsWith(y) );
   3: var compiled = expression.Compile();
   4: Console.WriteLine(compiled("First", "Second"));
   5: Console.WriteLine(compiled("First", "Fir"));

使用表达式树本身的话就要复杂多了:

   1: MethodInfo method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
   2: var target = Expression.Parameter(typeof(string), "x");
   3: var methodArg = Expression.Parameter(typeof(string), "y");
   4: Expression[] methodArgs = new[] { methodArg };
   5: Expression call = Expression.Call(target, method, methodArgs);
   6: var lambdaParameters = new[] { target, methodArg };
   7: var lambda = Expression.Lambda<Func<string,string,bool>>(call, lambdaParameters);
   8: var compiled = lambda.Compile();
   9: Console.WriteLine(compiled("First", "Second"));
  10: Console.WriteLine(compiled("First", "Fir"));

尽管上面的代码看起来更加的复杂, 不过其更清晰的展现了表达式树上有什么, 以及参数是如何被绑定的. 让我们从方法调用开始, 其构成了表达式的主体: 调用方法的目标, (换句话说, 调用StartsWith的字符串); 方法本身(作为MethodInfo); 参数列表(在这个例子中, 只有一个参数). 碰巧我们的例子我们方法调用目标和参数都是做为parameters传入到表达式当中的, 不过它们可以是其它的表达式类型——常量, 方法调用值, 属性值等等.

在将方法调用构建成表达式后, 我们需要将其转换成为lambda表达式, 绑定我们需要的参数. 我们重用了为方法调用而创建的相同的ParameterExpression值: 他们在lambda表达式中被指定的顺序就是最后调用委托它们被选取的顺序.

看看上面创建复杂的代码,仅仅是一个简单的方法调用, 可以想象如果是更复杂一点的表达式结果将会是如何——应该感到高兴的是, C# 3可以通过lambda表达式创建表达式树. 有一个小问题可以稍微提一下, C# 3 编译器通过类似上面的方式来创建表达式树, 不过它拥有一个快捷方式: 编译器并不会使用反射去获取String.StartsWith的MethodInfo, 相反的, 它使用与方法等价的typeof操作符, 这仅仅是在IL代码当中可用, C#本身并不支持, 而且从方法群组创建委托实例的时候也应用了相同的操作符.
现在我们看到了表达式树和lambda表达式是如何联系在一起的, 让我们来简短的看一下为什么它们如此有用.

LINQ的心脏——表达式树

如果没有lambda表达式, 表达式树只能有相对小得多的价值. 它仅仅是CodeDOM的另外一个选择, 尤其是当你只想建模一个单一表达式而不是完整的语句, 方法, 类型等等, 而且带来的好处相当有限.

反过来说也一样, 没有表达式树, lambda表达式用处也将少得多. 拥有一种更简洁的方式创建委托实例依然会是很受欢迎的, 一种更加函数式化的开发方式也还是可行的. 而当与扩展方法一起使用的时候, lambda表达式尤其有效. 然而, 当与表达式树一起的时候, 事情变得更加有趣.

那么当我们把lambda表达式, 表达式树和扩展方法组合在一起的时候得到了什么呢? 答案是LINQ. 在很长的一段时间内我们已经能够拥有很棒的编译时检查, 我们也可以告诉另外一个平台让其去运行一些代码, 它们通常是明文的(例如SQL 查询), 但我们从来没有能够在同一时间完成它们. lambda表达式提供了编译时检查, 而表达式树提供了运行时的抽象, 将它们捆绑在一起, 我们拥有了两个世界. LINQ provider可以从我们熟悉的编程语言中(例如C#)产生表达式树并作为中间格式, 其可以被转换为目标平台的本地语言,
例如SQL. LINQ to SQL provider就让我们可以使用C#来产生SQL语句.

抱歉!评论已关闭.