• 在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>类型的实例,其中T是委托类型,并为该实例分配一个lambda表达式。让我们看一下代码。

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

    Expression <Action <int >> printExpr =(arg)=> Console.WriteLine(arg);

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

    printExpr.Compile()(10);

    //打印10。

    在此示例中,C#编译器从提供的lambda表达式生成表达式树。请注意,如果使用Action <int>代替Expression <Action <int >>作为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<Action<int>>(

    methodCall,

    new ParameterExpression[] { param }

    ).Compile()(10);

    //同时显示10。

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

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

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

    Action<int> printTwoLines = (arg) =>

    {

    Console.WriteLine("Print arg:");

    Console.WriteLine(arg);

    };

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

    Expression<Action<int>> 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<Action<int>>(

    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<int, int> 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<Func<int, int>>(block, value).Compile();

    }

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

    static Func<int, int> 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<int, int>)method.CreateDelegate(typeof(Func<int, int>));

    }

  • 相关阅读:
    winform打开浏览器,并定位浏览器显示位置
    晋安河边赏花
    aop+注解防止表单重复提交
    Springboot使用LocalDateTime日期转换问题
    一种订单号生成规则
    给项目配置一个公共线程池
    国产芯片GPU, 需要你助力
    回顾10年职业生涯,重新启航
    httprunner运行报错load() missing 1 required positional argument: 'Loader'
    ETW概述
  • 原文地址:https://www.cnblogs.com/xianchengzhang/p/13434845.html
Copyright © 2020-2023  润新知