本地函数的代码被创建多少次?只调用一次或任何时候父函数?

问题描述

现代 C# 支持嵌套函数的定义。例如:

public bool Test(params int[] args) {
    bool isNumberValid(int i) {
        return i > 0;
   }
   foreach(var n in args) {
      if(!isNumberValid(n)) {
         return false;
      }
   }
   return true;
}

我写上面的例子只是作为一个测试用例场景,是否可以重构并不重要。现在的问题是,isNumberValid 函数创建了多少次?它是否只创建一次,从而被编译器移到父功能块之外?还是在调用函数时在运行时重新创建(作用域在父堆栈下)?

解决方法

如果您使用反编译器检查输出,您将看到如下内容:

public bool Test(params int[] args)
{
    bool flag;
    int[] numArray = args;
    int num = 0;
    while (true)
    {
        if (num >= (int)numArray.Length)
        {
            flag = true;
            break;
        }
        else if (Program.<Test>g__isNumberValid|1_0(numArray[num]))
        {
            num++;
        }
        else
        {
            flag = false;
            break;
        }
    }
    return flag;
}

这说明已经编译成单独的方法了,而且只编译了一次。

另请注意,对于本示例中如此小的本地函数,很可能(对于发布模式构建)JIT 编译器会内联该函数,因此甚至不会对其进行函数调用。

,

这是从 ILSpy 生成的 MSIL 代码。

如我们所见,本地方法被转换为与当前类的任何实例方法一样,并且代码只存在一次。

因此编译器“重构”了自己。

C# 本地方法不像 C 和 C++ inline functions 的代码根据用法重复。

本地方法允许重构代码并创建更干净的代码,同时不允许从包含它们的方法的外部调用、隔离处理和/或多次调用它们。

之前也有使用 ActionFunc 的本地 lambda。

也是 delegate,是所有事物的祖先。

Local functions (C# Programming Guide)

.method public hidebysig static bool Test (int32[] args) cil managed 
{
    .param [1]
        .custom instance void [mscorlib]System.ParamArrayAttribute::.ctor() = (01 00 00 00)
    .maxstack 2
    .locals init (
        [0] int32[],[1] int32,[2] int32 n,[3] bool,[4] bool
    )

    //  foreach (int i2 in args)
    //  {
    //      if (!isNumberValid(i2))
    //      {
    //          return false;
    //      }
    //  }
    IL_0003: ldarg.0
    // (no C# code)
    IL_0004: stloc.0
    IL_0005: ldc.i4.0
    IL_0006: stloc.1
    IL_0007: br.s IL_0026
    // loop start (head: IL_0026)
        IL_0009: ldloc.0
        IL_000a: ldloc.1
        IL_000b: ldelem.i4
        IL_000c: stloc.2
        // if (!isNumberValid(i2))
        IL_000e: ldloc.2
>>>>>>>>>>
        IL_000f: call bool ConsoleApp.Program::'<Test>g__isNumberValid|30_0'(int32)
>>>>>>>>>>
        IL_0014: ldc.i4.0
        IL_0015: ceq
        IL_0017: stloc.3
        IL_0018: ldloc.3
        // (no C# code)
        IL_0019: brfalse.s IL_0021

        // return false;
        IL_001c: ldc.i4.0
        IL_001d: stloc.s 4
        // (no C# code)
        IL_001f: br.s IL_0031

        IL_0022: ldloc.1
        IL_0023: ldc.i4.1
        IL_0024: add
        IL_0025: stloc.1

        IL_0026: ldloc.1
        IL_0027: ldloc.0
        IL_0028: ldlen
        IL_0029: conv.i4
        IL_002a: blt.s IL_0009
    // end loop

    // return true;
    IL_002c: ldc.i4.1
    IL_002d: stloc.s 4
    // (no C# code)
    IL_002f: br.s IL_0031

    IL_0031: ldloc.s 4
    IL_0033: ret
} // end of method Program::Test

这里的本地方法现在是一个类方法:

.method assembly hidebysig static bool '<Test>g__isNumberValid|30_0' (int32 i) cil managed 
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
      = (01 00 00 00)
    // Method begins at RVA 0x342c
    // Code size 10 (0xa)
    .maxstack 2
    .locals init (
        [0] bool
    )

    // return i > 0;
    IL_0001: ldarg.0
    IL_0002: ldc.i4.0
    IL_0003: cgt
    IL_0005: stloc.0
    // (no C# code)
    IL_0006: br.s IL_0008

    IL_0008: ldloc.0
    IL_0009: ret
} // end of method Program::'<Test>g__isNumberValid|30_0'

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...