在Visual Studio 2010中使用表达式树生成动态方法

在Visual Studio 2010中使用表达式树生成动态方法
表达式树首次出现在Visual Studio 2008中,主要由LINQ提供程序使用。您可以使用表达式树以树状格式表示代码,其中每个节点都是一个表达式。您还可以将表达式树转换为已编译的代码并运行它。通过这种转换,可以对可执行代码进行动态修改,并可以在各种数据库中执行LINQ查询并创建动态查询。Charlie Calvert的博客文章Expression Tree Basics中解释了Visual Studio 2008中的表达式树。

在本文中,我将展示如何在Visual Studio 2010中扩展表达式树,以及如何使用它们生成动态方法(以前只能通过发出MSIL才能解决的问题)。但是,尽管我强烈建议您首先阅读Charlie的博客文章,但我仍然需要重复一些基础知识以阐明某些细微差别。

创建表达式树
生成表达式树的最简单方法是创建一个Expression 类型的实例,其中T是委托类型,并为该实例分配一个lambda表达式。让我们看一下代码。

//通过提供lambda表达式来创建表达式树。

Expression > printExpr =(arg)=> Console.WriteLine(arg);

//编译并调用表达式树。

printExpr.Compile()(10);

//打印10。

在此示例中,C#编译器从提供的lambda表达式生成表达式树。请注意,如果使用Action 代替Expression >作为printExpr对象的类型,则不会创建任何表达式树,因为委托不会转换为表达式树。

但是,这不是创建表达式树的唯一方法。您还可以使用System.LINQ.Expressions命名空间中的类和方法。例如,您可以使用以下代码创建相同的表达式树。

//为表达式树创建一个参数。

ParameterExpression param = Expression.Parameter(typeof(int),“ arg”);

//为方法调用创建一个表达式并指定其参数。

MethodCallExpression methodCall = Expression.Call(

typeof(Console).GetMethod(“ WriteLine”,new Type [] {typeof(int)}),

param

);

//编译并调用methodCall表达式。

Expression.Lambda>(

methodCall,

new ParameterExpression[] { param }

).Compile()(10);

//同时显示10。

当然,这看起来要复杂得多,但这是在向表达式树提供lambda表达式时实际发生的。

表达式树与Lambda表达式
一个常见的误解是表达式树与lambda表达式相同。这不是真的。一方面,正如我已经展示的,您可以使用API方法来创建和修改表达式树,而完全不使用lambda表达式语法。另一方面,并非每个lambda表达式都可以隐式转换为表达式树。例如,多行lambdas(也称为语句lambdas)不能隐式转换为表达式树。

//您可以在委托中使用多行lambda。

Action printTwoLines = (arg) =>

{

Console.WriteLine("Print arg:");

Console.WriteLine(arg);

};

//但是在表达式树中,这会生成编译器错误。

Expression> printTwoLinesExpr = (arg) =>

{

Console.WriteLine("Print arg:");

Console.WriteLine(arg);

};

Visual Studio 2010中的表达树
到目前为止,我展示的所有代码示例在Visual Studio 2008和Visual Studio 2010中都可以工作(或不工作)。现在,让我们转到C#4.0和Visual Studio 2010。

在Visual Studio 2010中,扩展了表达式树API,并将其添加到动态语言运行库(DLR)中,以便语言实现者可以发出表达式树而不是MSIL。为了支持这个新目标,控制流和分配功能已添加到表达式树中:循环,条件块,try-catch块等。

有一个陷阱:您不能通过使用lambda表达式语法来“简单地”使用这些新功能。您必须使用表达式树API。因此,即使在Visual Studio 2010中,上一节中的最后一个代码示例仍然会生成编译器错误。

但是,现在您可以使用Visual Studio 2008中不提供的API方法来创建这种表达式树。这些方法之一是Expression.Block,它可以按顺序执行多个表达式,而这正是该方法我需要这个例子。

//为表达式树创建参数。

ParameterExpression param = Expression.Parameter(typeof(int),“ arg”);

//创建用于打印常量字符串的表达式。

MethodCallExpression firstMethodCall = Expression.Call(

typeof(Console).GetMethod(“ WriteLine”,new Type [] {typeof(String)}),

Expression.Constant(“ Print arg:”)

);

//创建用于打印传递的参数的表达式。

MethodCallExpression secondMethodCall = Expression.Call(

typeof(Console).GetMethod(“ WriteLine”,new Type [] {typeof(int)}),

param

);

//创建一个结合了两个方法调用的块表达式。

BlockExpression块= Expression.Block(firstMethodCall,secondMethodCall);

//编译并调用表达式树。

Expression.Lambda>(

block,

new ParameterExpression[] { param }

).Compile()(10);

我将重复一遍:尽管扩展了表达式树API,但是表达式树与lambda表达式语法一起工作的方式没有改变。这意味着Visual Studio 2010中的LINQ查询具有与Visual Studio 2008中相同的功能(和相同的限制)。

但是由于有了这些新功能,您可以在LINQ之外找到更多可以使用表达式树的区域。

生成动态方法
现在让我们转到新API可以提供帮助的实际问题上。最著名的一种是创建动态方法。解决此问题的常用方法是使用System.Reflection.Emit并直接与MSIL一起使用。不用说,生成的代码很难编写和阅读。

从本质上讲,将树两行打印到控制台的表达式树已经是动态方法的示例。但是,让我们尝试一些更复杂的示例,以演示新API的更多功能。感谢DLR团队的开发人员John Messerly提供了以下示例。

假设您有一个简单的方法来计算数字的阶乘。

static int CSharpFact(int value)

{

  int result = 1;

while (value > 1)

{

result *= value--;

}

return result;

}

现在,您需要一种可以执行相同操作的动态方法。我们这里有几个基本元素:传递给方法的参数,局部变量和循环。这是通过使用表达式树API来表示这些元素的方式。

static Func ETFact()

{

//创建参数表达式。

ParameterExpression value = Expression.Parameter(typeof(int), "value");

//创建一个表达式以保存局部变量。

ParameterExpression result = Expression.Parameter(typeof(int), "result");

//创建要从循环跳转到的标签。

LabelTarget label = Expression.Label(typeof(int));

//创建方法主体。

BlockExpression block = Expression.Block(

//添加局部变量。

new[] { result },

//将常量分配给局部变量:result = 1

Expression.Assign(result, Expression.Constant(1)),

//添加一个循环。

Expression.Loop(

//将条件块添加到循环中。

Expression.IfThenElse(

//条件:值> 1

Expression.GreaterThan(value, Expression.Constant(1)),

//如果为true:结果* =值-

Expression.MultiplyAssign(result,

Expression.PostDecrementAssign(value)),

//如果为false,则从循环退出并转到标签。

Expression.Break(label, result)

),

//跳转到的标签。

label

);

//编译表达式树并返回委托。

return Expression.Lambda>(block, value).Compile();

}

是的,这看起来比原始的C#代码更复杂,不清楚。但是,将其与生成MSIL所必须编写的内容进行比较。

static Func ILFact()

{

      var method = new DynamicMethod(

      "factorial", typeof(int),

      new[] { typeof(int) }

      );

      var il = method.GetILGenerator();

      var result = il.DeclareLocal(typeof(int));

      var startWhile = il.DefineLabel();

      var returnResult = il.DefineLabel();

      // result = 1

      il.Emit(OpCodes.Ldc_I4_1);

      il.Emit(OpCodes.Stloc, result);

      // if (value <= 1) branch end

      il.MarkLabel(startWhile);

      il.Emit(OpCodes.Ldarg_0);

      il.Emit(OpCodes.Ldc_I4_1);

      il.Emit(OpCodes.Ble_S, returnResult);

      // result *= (value--)

      il.Emit(OpCodes.Ldloc, result);

      il.Emit(OpCodes.Ldarg_0);

      il.Emit(OpCodes.Dup);

      il.Emit(OpCodes.Ldc_I4_1);

      il.Emit(OpCodes.Sub);

      il.Emit(OpCodes.Starg_S, 0);

      il.Emit(OpCodes.Mul);

      il.Emit(OpCodes.Stloc, result);

      // end while

      il.Emit(OpCodes.Br_S, startWhile);

      // return result

      il.MarkLabel(returnResult);

      il.Emit(OpCodes.Ldloc, result);

      il.Emit(OpCodes.Ret);

      return (Func)method.CreateDelegate(typeof(Func));

}

你可能感兴趣的:(在Visual Studio 2010中使用表达式树生成动态方法)