简单示例中的C# Linq表达式

目录

介绍

简单表达式与块表达式

简单(non-Block)表达式

最简单的表达式示例

WrongConstantAssignmentSample

CorrectedConstantAssignmentSample

ConstantAssignmentSampleWithPrintingResult

构建non-Block表达式的原则

具有返回值的Non-Block表达式

SimpleReturnConstantSample

ReturnSumSample

带循环的Non-Block表达式

LoopSample

LoopForCopyingArrayValuesSample

LoopSumUpNumbersFromToSample

使用Expression.Block方法的表达式

BlockSampleWithLocalVariableAndLineByLineCode

BlockLoopSumUpNumbersFromToSample

BlockCallPlusRefSample

结论


介绍

最有用但同时文档记录不佳的C#内置库之一是System.Linq.Expressions。它允许创建复杂的C#代码,将其动态编译为lambda表达式,然后以编译的代码速度运行这些lambda表达式——比使用System.Reflection库实现相同的速度快很多倍。

表达式编码与编写通常的C#代码非常不同,需要剧烈的上下文切换。

我一直在时不时地使用表达式,不是经常,每次使用表达式时,我都必须花一些时间来记住表达式编码的基础知识。

本文的目的是提供信息和示例,使我和其他人将来更容易切换到表达式编码的上下文。

本文的代码示例位于GithubNP.Samples/ExpressionsNP.Samples存储库的Expressions文件夹下。

简单表达式与块表达式

简单表达式是那些没有Expression.Block(...)方法构建的表达式。此类表达式更简单,通常足以满足基本目的,但具有以下限制:

  1. 组合non-block(简单)表达式是通过将更简单的表达式与Expression类中的static方法(Expression.Block(...)方法除外)组合来构建的。这种递归表达式构建会产生所谓的表达式树。生成的代码本质上是通过递归地将更简单的表达式组合替换为更复杂的表达式而获得的单行代码。没有办法正确模仿C#中常见的逐行代码。
  2. 无法为生成的lambda表达式定义和初始化局部变量。您将在下面的示例中看到,我们必须为non-block表达式创建具有基本上不需要的参数的lambda。
  3. 如果不使用Block表达式,就无法创建包含一些refout参数的对方法的调用的表达式,正是因为只有局部变量可以用作refout参数。

简单(non-Block)表达式

大多数内置表达式功能来自System.Linq.Expressions包中的Expressionstatic方法。

基元表达式或内置表达式是使用Expression类的static工厂方法生成的表达式,例如:

  • Expression.Constant(5, typeof(int))将为int类型的常量5创建一个表达式。
  • Expression.Parameter(typeof(double), "var1"))将为名为"var1"double类型的变量创建一个表达式。

就其本身而言,原始(内置)表达式在很大程度上是无用的,但是Expression类还提供了将它们组合成组合表达式并将组合表达式编译为有用的lambdastatic方法。

打开控制台应用程序SimpleExpressionsExamples/NP.Samples.Expressions.SimpleExpressionsExamples.sln

查看主文件Program.cs。文件的内容由static方法组成;每个对应于一个样本。在文件末尾,注释掉的方法调用的编写顺序与定义它们的顺序相同。使用示例时,取消注释相应的方法并运行它;完成后,为了清楚起见,请将其注释掉。

请注意,每个表达式的DebugView属性仅在调试器中可见,该调试器显示表达式的代码并不完全是C#,但对于C#开发人员来说非常相似且易于理解。此属性可以并且应该用于检查和调试表达式。

最简单的表达式示例

在本小节中,我们将介绍不带return语句和循环的简单表达式示例。

WrongConstantAssignmentSample

第一个示例方法称为WrongConstantAssignmentSample()。它的名称以Wrong开头,因为它会引发异常。

该方法演示如何将常量分配给变量:

static void WrongConstantAssignmentSample()
{
    // Create variable 'myVar' of type 'int'
    var paramExpression = Expression.Parameter(typeof(int), "myVar");

    // create constant 5
    var constExpression = Expression.Constant(5, typeof(int));

    // assign constant to the variable
    Expression<Action<int>> lambdaExpr =
        Expression.Lambda<Action<int>>
        (
            assignExpression, // lambda body expression
            paramExpression   // lambda input parameter expression
        );

    // create lambda expression
    Expression<Action> lambdaExpr = 
        Expression.Lambda<Action>(assignExpression);

    // compile lambda expression
    Action lambda = lambdaExpr.Compile();

    // run lambda
    lambda();
}

取消注释并运行Program.cs文件底部的方法调用后,您将看到它在lambdaExpr.Compile()行中抛出异常,异常消息为:

System.InvalidOperationException: 'variable 'myVar' of type 'System.Int32' 
       referenced from scope '', but it is not defined'  

进入调试器中WrongConstantAssignmentSample()的作用域,在调试器中打开lambdaExpr变量并查看其DebugView属性的内容:

.Lambda #Lambda1<System.Action>() {
    $myVar = 5
}

如上所述,DebugView属性(仅在调试器中可用)包含表达式代码的详细视图(不完全是C#,但非常接近),可用于调试和修复它。

人们可以立即看到上面的表达式有什么问题——myVar变量从未定义过。

除了我们将在后面描述的Block表达式之外,没有办法在表达式中定义局部变量。

相反,我们可以为lambda定义一个参数,并将其用作lambda表达式中的变量,如下所示。

CorrectedConstantAssignmentSample

看看CorrectedConstantAssignmentSample()方法:

static void CorrectedConstantAssignmentSample()
{
    // Create variable 'myVar' of type 'int'
    var paramExpression = Expression.Parameter(typeof(int), "myVar");

    // create constant 5
    var constExpression = Expression.Constant(5, typeof(int));

    // assign constant to the variable
    var assignExpression = Expression.Assign(paramExpression, constExpression);

    // assign constant to the variable
    Expression<Action<int>> lambdaExpr =
        Expression.Lambda<Action<int>>
        (
            assignExpression, // lambda body expression
            paramExpression   // lambda input parameter expression
        );
    ///.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $myVar) {
    ///    $myVar = 5
    ///}

    // compile lambda expression
    Action<int> lambda = lambdaExpr.Compile();

    // run lambda (pass any int number to it)
    lambda(0);
}

请注意,结果paramExpression使用了两次:

  1. 作为作业的左侧部分:Expression.Assign(paramExpression, constExpression);
    在C#代码中,这将是myVar = 5;
  2. 作为lambda表达式的输入参数:Expression.Lambda<Action<int>>(assignExpression, <b>paramExpression</b>);
    在C#中,这看起来像Lambda(myVar);

看看我们如何获得lambda表达式lambdaExpr

// create lambda expression (now it has an input parameter)
Expression<Action<int>> lambdaExpr =
    Expression.Lambda<Action<int>>
    (assignExpression/*body expression*/, paramExpression /* input arg expression */);

我们使用Action<...>作为ExpressionType参数。其Action<...>中的类型数量应等于lambda的输入参数数量(在此示例中——它只是一个整数参数,因此我们有Action<int>)。

我们传递给Expression.Lambda<Action<int>>(...)的参数是:lambda主体表达式,后跟输入参数表达式,其顺序与参数传递给lambda的顺序相同。当然,输入参数表达式的数量应该与类型参数Action<...>的数量相同,并且它们的类型应该匹配。

在此示例中——方法主体由assignExpression表示,而单个整数输入参数由 paramExpression表示。

下面是此示例的lambda表达式DebugView代码:

.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $myVar) {
    $myVar = 5
} 

重要说明:如上所述,我们不能在不使用Block表达式的情况下定义局部变量,因此,myVar变量被定义为lambda表达式的输入参数(参数),即使从C#的角度来看——输入参数在其他方面完全无用:它不能在方法之外产生任何变化,因为它是按值传递的。定义局部变量是使用Block表达式的原因之一——这将在下面详细介绍。

我们不再得到例外,但没有迹象表明分配确实发生了。程序在不生成任何控制台输出的情况下运行。

ConstantAssignmentSampleWithPrintingResult

在下一个示例中,我们将演示调用Console.WriteLine(int i)方法来打印变量的结果值。

static void ConstantAssignmentSampleWithPrintingResult()
{
    // Create variable 'myVar' of type 'int'
    var paramExpression = Expression.Parameter(typeof(int), "myVar");

    // create constant 5
    var constExpression = Expression.Constant(5, typeof(int));

    // assign constant to the variable
    var assignExpression = Expression.Assign(paramExpression, constExpression);

    // get method info for a Console.WriteLine(int i) method
    MethodInfo writeLineMethodInfo = 
        typeof(Console).GetMethod(nameof(Console.WriteLine), new Type[] {typeof(int)})!;

    // we create an expression to call Console.WriteLine(int i)
    var callExpression = Expression.Call(writeLineMethodInfo, assignExpression);

    // create lambda expression (now it has an input parameter)
    Expression<Action<int>> lambdaExpr =
        Expression.Lambda<Action<int>>
    (
            callExpression, /* lambda body expression */
            paramExpression /* input parameter expression */
    );
    ///.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $myVar) {
    ///    .Call System.Console.WriteLine($myVar = 5)
    ///}

    // compile lambda expression
    Action<int> lambda = lambdaExpr.Compile();

    // run lambda (pass any int number to it)
    lambda(0);
}

以下是我们如何添加调用Console.WriteLine(int i)以打印以下assignExpression结果:

// get method info for a Console.WriteLine(int i) method
MethodInfo writeLineMethodInfo = 
    typeof(Console).GetMethod(nameof(Console.WriteLine), new Type[] {typeof(int)})!;

// we create an expression to call Console.WriteLine(int i)
var callExpression = Expression.Call(writeLineMethodInfo, assignExpression);

运行此ConstantAssignmentSampleWithPrintingResult()方法会将数字5打印到控制台。

Lambda表达式的DebugView是:

.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $myVar) {
    .Call System.Console.WriteLine($myVar = 5)
} 

构建non-Block表达式的原则

上面的示例说明了从简单表达式构建复杂表达式的原则——将较简单的表达式作为参数传递给一些Expression静态方法。

在最后一个示例中,我们使用Expression.Call(...)方法构建一个callExpression表达式并传递给它assignExpression,而该方法又通过处理另外两个表达式的Expression.Assign(...)方法获得——paramExpressionconstExpression。简而言之,我们有以下用于调用表达式的表达式树(这是我们的Lambda表达式的主体):

表达式树生成的lambda以递归方式将父表达式的结果传递给子表达式定义的转换。

除了块表达式之外的每个表达式本质上都是一行——尽管这样的行(是组合和扩展其他表达式的结果)可能很长。

具有返回值的Non-Block表达式

表达式(简单和Block)可以返回值(与非void方法相同)。编译这样的表达式将导致Func<...>而不是Action<...> lambda

您唯一需要做的不同事情是创建一个返回值的lambda表达式,即传递Func<...>而不是Action<...>Expression.Lambda作为lambda类型参数。与往常一样,Func<...>最后一个类型参数应指定返回类型。

当然,还有一个条件是lambda主体表达式返回一些值,但大多数简单的表达式都会这样做。

在本小节中,我们将提供此类方法的示例。

SimpleReturnConstantSample

看看SimpleReturnConstantSample() static方法:

static void SimpleReturnConstantSample()
{
    // create a lambda expression returning integer 1234
    var lambdaExpr = Expression.Lambda<Func<int>>
                     (Expression.Constant(1234, typeof(int)));
    ///.Lambda #Lambda1<System.Func`1[System.Int32]>() {
    ///    1234
    ///}

    // compile lambda expression
    var lambda = lambdaExpr.Compile();

    // lambda returns 1234
    int returnedNumber = lambda();

    // 1234 is printed to console
    Console.WriteLine(returnedNumber);
} 

运行它将打印1234到控制台。

ReturnSumSample

ReturnSumSample() static方法将传递给它的两个整数输入参数相加并返回结果:

static void ReturnSumSample()
{
    // integer input parameter i1
    var i1Expr = Expression.Parameter(typeof(int), "i1");

    // integer input parameter i2
    var i2Expr = Expression.Parameter(typeof(int), "i2");

    // sum up two numbers expression
    var sumExpr = Expression.Add(i1Expr, i2Expr);
    //$i1 + $i2

    // lambda expression that sums up two  numbers and returns the result
    var sumLambdaExpr = 
        Expression.Lambda<Func<int, int, int>>
        (
            sumExpr, // lambda body expression
            i1Expr,  // first int parameter i1 expression
            i2Expr   // second int parameter i2 expression
       );
    ///.Lambda #Lambda1<System.Func`3[System.Int32,System.Int32,System.Int32]>(
    ///    System.Int32 $i1,
    ///    System.Int32 $i2)
    ///{
    ///    $i1 + $i2
    ///}

    // compile lambda expression
    var sumLambda = sumLambdaExpr.Compile();

    int i1 = 1, i2 = 2;

    // run lambda (i1 + i2)
    int result = sumLambda(i1, i2);

    // print the result
    Console.WriteLine($"{i1} + {i2} = {result}");
}

上面代码中最有趣的部分是:

  1. Expression.Add(param1Expr, param2Expr)方法创建的新简单表达式
  2. 创建lambda表达式:

// lambda expression that sums up two  numbers and returns the result
var sumLambdaExpr = 
    Expression.Lambda<Func<int, int, int>>
    (
        sumExpr, // lambda body expression
        i1Expr,  // first int parameter i1 expression
        i2Expr   // second int parameter i2 expression
   );  

请注意,我们使用Func<int, int, int>Action<int, int>不作为泛型类型参数给Expression.Lambda

带循环的Non-Block表达式

循环允许使用循环控制流创建表达式。

LoopSample

看看LoopSample()

static void LoopSample()
{
    // loop index
    var loopIdxExpr = Expression.Parameter(typeof(int), "i");

    // max loop index plus 1
    var loopIdxToBreakOnExpr = Expression.Parameter(typeof(int), "loopIdxToBreakOn");

    // label with return type int will be returned when loop breaks. 
    LabelTarget breakLabel = Expression.Label(typeof(int), "breakLoop");

    // loop expression 
    var loopExpression =
        Expression.Loop 
        (
            // if then else expression
            Expression.IfThenElse(
                Expression.LessThan(loopIdxExpr, 
                           loopIdxToBreakOnExpr), // if (i < loopIdxToBreakOn)
                Expression.PostIncrementAssign(loopIdxExpr),   //     i++;
                Expression.Break(breakLabel, loopIdxExpr)      // else return i;
            ),
            breakLabel
        );
    //.Loop
    //{
    //    .If($i < $loopIdxToBreakOn) {
    //        $i++
    //    } .Else {
    //        .Break #Label1 { $i }
    //    }
    //}
    //.LabelTarget #Label1:

    var lambdaExpr = Expression.Lambda<Func<int, int, int>>
    (
        loopExpression,        // loop lambda expression body
        loopIdxExpr,           // loop index (we cannot define it as a local variable, 
                               // so, instead we pass it as an input arg)
        loopIdxToBreakOnExpr   // input arg expression specifying the number 
                               // to break on when loop index reaches it.
    );
    //.Lambda #Lambda1<System.Func`3[System.Int32,System.Int32,System.Int32]>(
    //    System.Int32 $i,
    //    System.Int32 $loopIdxToBreakOn) 
    //{
    //    .Loop  {
    //        .If($i < $loopIdxToBreakOn) {
    //            $i++
    //        } .Else {
    //            .Break #breakLoop { $i }
    //        }
    //    }
    //    .LabelTarget #breakLoop:
    //}
    var lambda = lambdaExpr.Compile();

    int result = lambda(0, 5);

    // should print 5
    Console.WriteLine(result);
}  

简单循环从0迭代到5,然后返回5。返回结果将打印到控制台。该循环实质上模仿以下C#代码:

while(true)
{
    if (i < loopIdxToBreakOn)
        i++;
    else
        break;
}
return i;

请注意,i变量不是由循环定义的。这是一个常见问题——无法在非块表达式中定义局部变量。

由于我们不能将循环索引i定义为局部变量,因此我们必须将其定义为lambda的输入参数(即使没有任何其他需要这样做)。

代码中最有趣的部分是:

// label with return type int will be returned when loop breaks. 
LabelTarget breakLabel = Expression.Label(typeof(int), "breakLoop");

// loop expression 
var loopExpression =
    Expression.Loop 
    (
        // if then else expression
        Expression.IfThenElse(
            Expression.LessThan(loopIdxExpr, 
                       loopIdxToBreakOnExpr),             // if (i < loopIdxToBreakOn)
            Expression.PostIncrementAssign(loopIdxExpr),  //     i++;
            Expression.Break(breakLabel, loopIdxExpr)     // else return i;
        ),
        breakLabel
    ); 

Expression.Label允许创建一个'goto'标签,该标签将指定从循环中断后代码执行恢复的位置。它可以是void,也可以是返回类型(在我们的例子中是int)。

Expression.IfThenElse(...)组合了其他三个表达式——指定循环条件、循环操作以及不再满足循环条件时发生的情况。在我们的例子中,它会产生大约以下代码:

if (i < loopIdxToBreakOn) // loop condition
{
    i++;                  // loop action
}
else
{
    return i;             // return i if loop condition is not satisfied
}

LoopForCopyingArrayValuesSample

我们的下一个示例LoopForCopyingArrayValuesSample()演示如何创建一个表达式以将一个int[]数组的内容复制到另一个数组中。数组(sourceArraytargetArraty)作为输入参数传递给lambda。假定数组是非null数组并且大小相同,但为了简单起见,我们不在表达式lambda中检查它。

static void LoopForCopyingArrayValuesSample()
{
    // assume that the passed arrays are of the same length

    // source array expression
    var sourceArrayExpr = Expression.Parameter(typeof(int[]), "sourceArray");

    // target array expression
    var targetArrayExpr = Expression.Parameter(typeof(int[]), "targetArray");

    // array cell index (we have to pass it as an input arg 
    // since there are no local variables)
    var arrayCellIdxExpr = Expression.Parameter(typeof(int), "i");

    // source array length
    var arrayLengthExpr = 
        Expression.ArrayLength(sourceArrayExpr); // sourceArray.Length

    // we do not specify the label type, so loopLabel is void
    var loopLabel = Expression.Label("breakLabel");

    var loopExpr =
        Expression.Loop
        (
            Expression.IfThenElse
            (
                Expression.LessThan(arrayCellIdxExpr, 
                           arrayLengthExpr),    // if (i < sourceArray.Length)
                Expression.Assign(
                    Expression.ArrayAccess(targetArrayExpr, 
                           arrayCellIdxExpr),   //     targetArray[i] = 
                    Expression.ArrayAccess(sourceArrayExpr, 
                    Expression.PostIncrementAssign
                           (arrayCellIdxExpr))  //sourceArray[i++];
                ),
                Expression.Break(loopLabel)     // else break;
            ),
            loopLabel
        );
    //.Loop  
    //{
    //    .If($i < $sourceArray.Length) {
    //        $targetArray[$i] = $sourceArray[$i++]
    //    } .Else {
    //        .Break #breakLabel { }
    //    }
    //}
    //.LabelTarget #breakLabel:


    // unnecessary lambda parameter - arrayCellIdxExpr since we cannot define 
    // and instantiate a local variable without Block expression
    var arrayCopyLambdaExpr = 
        Expression.Lambda<Action<int[], int[], int>>
        (loopExpr, sourceArrayExpr, targetArrayExpr, arrayCellIdxExpr);
    //.Lambda #Lambda1<System.Action`3[System.Int32[],System.Int32[],System.Int32]>(
    //    System.Int32[] $sourceArray,
    //    System.Int32[] $targetArray,
    //    System.Int32 $i) 
    //{
    //    .Loop  
    //    {
    //        .If($i < $sourceArray.Length) {
    //            $targetArray[$i] = $sourceArray[$i++]
    //        } .Else {
    //            .Break #breakLabel { }
    //        }
    //    }
    //    .LabelTarget #breakLabel:
    //}

    var arrayCopyLambda = arrayCopyLambdaExpr.Compile();

    int[] sourceArray = Enumerable.Range(1, 10).ToArray();

    int[] targetArray = new int[10];

    arrayCopyLambda(sourceArray, targetArray, 0);

    // will print: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    Console.WriteLine(string.Join(", ", targetArray));
}  

该方法会将targetArray(从110的数字)的内容打印到控制台。

请注意,与非块表达式一样,我们将循环索引变量i作为参数传递,因为我们无法将其定义为局部变量。

代码中最有趣(新)的部分是循环中IfThenElse表达式中的Assign表达式:

Expression.Assign(
    Expression.ArrayAccess(targetArrayExpr, arrayCellIdxExpr), // targetArray[i] = 
    Expression.ArrayAccess(sourceArrayExpr, 
    Expression.PostIncrementAssign(arrayCellIdxExpr))          // sourceArray[i++];
),  

它显示了使用ArrayAccess表达式访问数组单元格。下面是生成的C#代码

targetArray[i] = sourceArray[i++]; 

请注意,我们被迫在数组索引运算符[]中递增i,因为如果没有Block表达式,我们不能有多个行if语句正文。

LoopSumUpNumbersFromToSample

最后一个非块循环示例将展示如何对to整数变量指定的从0到值的结果整数求和:

static void LoopSumUpNumbersFromToSample()
{
    // loop index expression
    var loopIdxExpr = Expression.Parameter(typeof(int), "i");

    // max index to iterate to
    var toExpr = Expression.Parameter(typeof(int), "to");

    // result 
    var resultExpr = Expression.Parameter(typeof(int), "result");

    // of type int returns the integer result
    var loopLabel = Expression.Label(typeof(int), "breakLabel");

    var loopExpr =
        Expression.Loop
        (
            Expression.IfThenElse
            (
                Expression.LessThanOrEqual(loopIdxExpr, toExpr),// if (i < to)
                Expression.AddAssign(resultExpr, 
                Expression.PostIncrementAssign(loopIdxExpr)),   // result += i++;
                Expression.Break(loopLabel, resultExpr)         // else break the 
                                                          // loop and return result
            ),
            loopLabel
        );
    //.Loop  
    //{
    //    .If($i <= $to) {
    //        $result += $i++
    //    } .Else {
    //        .Break #breakLabel { $result }
    //    }
    //}
    //.LabelTarget #breakLabel:

    // unnecessary lambda parameter - resultExpr since we cannot define 
    // and instantiate a local variable without Block expression
    var sumNumbersFromTooLambdaExpr = Expression.Lambda<Func<int, int, int, int>>
                                      (loopExpr, loopIdxExpr, toExpr, resultExpr);
    //.Lambda #Lambda1<System.Func`4[System.Int32,System.Int32,System.Int32,
    // System.Int32]>(
    //    System.Int32 $i,
    //    System.Int32 $to,
    //    System.Int32 $result) 
    //{
    //    .Loop  {
    //        .If($i <= $to) {
    //            $result += $i++
    //        } 
    //        .Else {
    //             .Break #breakLabel { $result }
    //        }
    //    }
    //    .LabelTarget #breakLabel:
    //}

    var sumNumbersFromTooLambda = sumNumbersFromTooLambdaExpr.Compile();

    int from = 1; 
    int to = 10;

    var sumResult = sumNumbersFromTooLambda(from, to, 0);

    // prints 'Sum of integers from 1 to 10 is 55'
    Console.WriteLine($"Sum of intergers from {from} to {to} is {sumResult}");
}

代码的有趣部分是循环的主体:

Expression.AddAssign(resultExpr, 
           Expression.PostIncrementAssign(loopIdxExpr)),   // result += i++;  

这将生成以下C#代码:

result += i++;  

请注意,由于我们无法定义和初始化局部变量,因此我们必须将变量result的初始值0作为输入参数传递给lambda

使用Expression.Block方法的表达式

如本文开头所述,具有块方法(或块表达式)的表达式比简单(非块)表达式更强大。特别是,他们

  1. 允许逐行语句。
  2. 允许定义不需要作为输入参数传递的局部变量。
  3. 可以使用refout参数包装对方法的调用。

块表达式示例的所有代码都位于 BlockExpressionsExamples/NP 的程序.cs文件中。Samples.Expressions.BlockExpressionsExamples.sln solution.

Program.cs文件的内容由每个static方法对应于单个样本组成。在文件末尾,注释掉的方法调用的编写顺序与定义它们的顺序相同。使用示例时,取消注释相应的方法并运行它;完成后,为了清楚起见,请将其注释掉。

BlockSampleWithLocalVariableAndLineByLineCode

// the block expression below will 
// return the following code (i1 + i2)^2
static void BlockSampleWithLocalVariableAndLineByLineCode()
{
    var i1Expr = Expression.Parameter(typeof(int), "i1");
    var i2Expr = Expression.Parameter(typeof(int), "i2");

    // local variable 'result' expression
    var resultExpr = Expression.Parameter(typeof(int), "result");

    var blockExpr =
        Expression.Block
        (
            typeof(int),                              // return type of the block 
                                                      // (skip parameter if void)
            new ParameterExpression[] { resultExpr }, // local variable(s)
            Expression.Assign(resultExpr, 
            Expression.Add(i1Expr, i2Expr)),          // result = i1 + i2; 
                                                      // line 1 of block expr
            Expression.MultiplyAssign
              (resultExpr, resultExpr),               // result *= result; 
                                                      // line 2
            resultExpr                                // return result;    
                                                      // line 3
        );

    var lambdaExpr = Expression.Lambda<Func<int, int, int>>
    (
        blockExpr,                         // lambda body expression 
        i1Expr,                            // lambda input arg 1 
        i2Expr                             // lambda input arg 2
    );

    var lambda = lambdaExpr.Compile();

    int i1 = 1, i2 = 2;
    var result = lambda(i1, i2); // (1 + 2)^2 = 9

    // should print (1 + 2) * (1 + 2) = 9
    Console.WriteLine($"({i1} + {i2}) * ({i1} + {i2}) = {result} ");
}

生成的lambdaC#代码大约为:

(int i1, int i2) =>
{
    int result;
    result = i1 + i2;
    result *= result;

    return result;
}

当然,没有多行且没有局部变量的非块表达式也可以实现相同的效果,结果如下:

(int i1, int i2) => (i1 + i2) * (i1 + i2)

因此,我们仅介绍此示例来演示块表达式功能。

该方法的新部分是:

var blockExpr =
    Expression.Block
    (
        typeof(int),                              // return type of the block 
                                                  // (skip parameter if void)
        new ParameterExpression[] { resultExpr }, // local variable(s)
        Expression.Assign(resultExpr, 
        Expression.Add(i1Expr, i2Expr)),          // result = i1 + i2; 
                                                  // line 1 of block expr
        Expression.MultiplyAssign
            (resultExpr, resultExpr),             // result *= result; 
                                                  // line 2
        resultExpr                                // return result;    
                                                  // line 3
    );  

让我们仔细看看上面使用的Expression.Block(...)方法。

Block(...)的第一个参数指定块的返回类型(在我们的例子中是int)。 

第二个参数是指定块的局部变量的ParameterExpression对象数组。在我们的例子中,只有一个int类型的局部变量result

之后,有对应于代码行的表达式。

重要说明:将返回最后一行表达式(除非是void)——在我们的例子中,返回result变量,因为resultExpr是代码的最后一行。

BlockLoopSumUpNumbersFromToSample

我们的下一个示例类似于上面解释的LoopSumUpNumbersFromToSample()  non-Block示例。它产生带有fromto整数输入参数的Lambda,它将从fromto的所有整数相加,包括两个端点。

与非块表达式不同:

  • 它不需要将结果作为外部输入参数传递(结果定义为局部变量)
  • 它允许初始化结果并在单独的行上递增循环索引。

这是代码:

static void BlockLoopSumUpNumbersFromToSample()
{
    // loop index expression
    var loopIdxExpr = Expression.Parameter(typeof(int), "i");

    // max index to iterate to
    var toExpr = Expression.Parameter(typeof(int), "to");

    // result 
    var resultExpr = Expression.Parameter(typeof(int), "result");

    // of type int returns the integer result
    var loopLabel = Expression.Label(typeof(int), "breakLabel");

    var blockExpr =
        Expression.Block
        (
            typeof(int),                                // returns int
            new ParameterExpression[] { resultExpr },   // resultExpr is the 
                                                        // local variable expression
            Expression.Assign
                  (resultExpr, Expression.Constant(0)), // result = 0; 
                                                        // initialize result
            Expression.Loop
            (
                Expression.IfThenElse
                (
                    Expression.LessThanOrEqual(loopIdxExpr, toExpr), // if (i < to) {
                    Expression.Block // block containing multiple lines of expressions
                                     // (it is more clearer 
                                     // when i++ is on a separate line)
                    (
                        Expression.AddAssign
                           (resultExpr, loopIdxExpr),         // result += i;
                        Expression.PostIncrementAssign
                                     (loopIdxExpr)            // i++;
                    ),
                    Expression.Break(loopLabel, resultExpr)   // } else break the loop
                                                              // and return result
                ),
                loopLabel
            )
        );

    // unnecessary lambda parameter - resultExpr since we cannot define 
    // and instantiate a local variable without Block expression
    var sumNumbersFromTooLambdaExpr = Expression.Lambda<Func<int, int, int>>
                                      (blockExpr, loopIdxExpr, toExpr);

    var sumNumbersFromTooLambda = sumNumbersFromTooLambdaExpr.Compile();

    int from = 1;
    int to = 10;

    var sumResult = sumNumbersFromTooLambda(from, to);

    // prints 'Sum of integers from 1 to 10 is 55'
    Console.WriteLine($"Sum of intergers from {from} to {to} is {sumResult}");
}  

有趣的部分是:

var blockExpr =
    Expression.Block
    (
        typeof(int), // returns int
        new ParameterExpression[] { resultExpr },              // resultExpr is the 
                                                               // local variable 
                                                               // expression
        Expression.Assign(resultExpr, Expression.Constant(0)), // result = 0; 
                                                               // initialize result
        Expression.Loop
        (
            Expression.IfThenElse
            (
                Expression.LessThanOrEqual(loopIdxExpr, toExpr), // if (i < to) {
                Expression.Block // block containing multiple lines of expressions
                                 // (it is more clearer when i++ is on a separate line)
                (
                    Expression.AddAssign(resultExpr, loopIdxExpr), // result += i;
                    Expression.PostIncrementAssign(loopIdxExpr)    // i++;
                ),
                Expression.Break(loopLabel, resultExpr)            // } else break 
                                                                   // the loop and 
                                                                   // return result
            ),
            loopLabel
        )
    );  

Expression.Block(...)使用两次。顶层Expression.Block(...)定义resultExpr为局部变量,Expression.Assign(resultExpr, Expression.Constant(0))将其初始化为零调用循环并返回循环结果。

循环中的Block表达式表示循环主体,用于将i++增量运算符放置在单独的行result += i;后。在C#中,为了清楚起见,我更喜欢它,即使它将编译为相同的代码。

运行示例方法将导致:

Sum of integers from 1 to 10 is 55

打印到控制台。

BlockCallPlusRefSample

最后一个块示例将演示如何使用块表达式调用带有ref参数的方法。

要调用的方法为:

public static void PlusRef(ref int i1, int i2)
{
    i1 += i2;
}

第一个参数i1ref并用i1i2的总和进行更新。

此示例的目的是创建一个lambda,将此方法包装在接近以下内容的内容中:

(int i1, int i2) =>
{
    int result = i1;
    PlusRef(ref result, i2);

    return result;
}

请注意,lambda不能refout参数,因此我们被迫将输入作为简单int参数传递并返回int。变量result必须作为ref int参数传递给PlusRef(...)方法,因此它必须是局部变量(因为它不能作为ref参数传递给lambda)。

为了能够声明和初始化局部变量,我们被迫使用Block表达式。

static void BlockCallPlusRefSample()
{
    // i1 argument expression
    var i1Expr = Expression.Parameter(typeof(int), "i1");

    // i2 argument expression
    var i2Expr = Expression.Parameter(typeof(int), "i2");

    // local variable 'result' expression
    var resultExpr = Expression.Parameter(typeof(int), "result");

    Type plusRefMethodContainer = typeof(Program);

    // PlusRef(...) MethodInfo
    MethodInfo plusRefMethodInfo =
        plusRefMethodContainer.GetMethod(nameof(PlusRef))!;

    // block expression
    var blockExpr = Expression.Block
    (
        typeof(int), // block return type
        new ParameterExpression[] { resultExpr },               // int result; 
                                                                // local variable
        Expression.Assign(resultExpr, i1Expr),                  // result = i1;
        Expression.Call(plusRefMethodInfo, resultExpr, i2Expr), // call PlusRef
                                                                // (ref result, i2)
        resultExpr                                              // return result;
    );

    var lambdaExpr =
        Expression.Lambda<Func<int, int, int>>
        (
            blockExpr, // lambda body expression
            i1Expr,    // i1 parameter expression
            i2Expr     // i2 parameter expression
        );

    var lambda = lambdaExpr.Compile();

    int i1 = 1, i2 = 2;
    int result = lambda(i1, i2);

    Console.WriteLine($"{i1} + {i2} = {result}");
    // prints 1 + 2 = 3 to console
}  

结论

在本文中,我解释了基础知识并给出了许多编程System.Linq.Expressions的实践示例,介绍了非块和块表达式,解释了表达式的概念,并介绍了Expression类中用于构建各种表达式的众多static方法。

https://www.codeproject.com/Articles/5345657/Csharp-Linq-Expressions-in-Easy-Samples

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值