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

C# 本地函数的代码创建了多少次?只调用一次或任何时候调用父函数?,c#,local-functions,C#,Local Functions,现代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; } 我写上面的例子只是作为一个测试用例场景

现代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;
}
公共布尔测试(参数int[]args)
{
布尔旗;
int[]numArray=args;
int num=0;
while(true)
{
如果(num>=(int)numArray.Length)
{
flag=true;
打破
}
else if(Program.g_uisnumbervalid | 1_0(numArray[num]))
{
num++;
}
其他的
{
flag=false;
打破
}
}
返回标志;
}
这表明它已编译为单独的方法,并且只编译了一次


还要注意的是,对于本例中这样一个小的本地函数,JIT编译器很可能(对于发布模式构建)将内联该函数,因此甚至不会对其进行函数调用。

以下是ILSpy生成的MSIL代码

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

因此,编译器将自己“重构”

C#局部方法不像C和C++那样根据使用情况重复代码

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

以前还提供了使用和的本地lambda

同时,作为所有人的祖先

下面是现在作为类方法的本地方法:

.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'
。方法程序集隐藏静态bool'g_uuIsNumberValid | 30_0'(int32 i)cil托管
{
.custom instance void[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00)
//方法从RVA 0x342c开始
//代码大小10(0xa)
.maxstack 2
.init(
[0]布尔
)
//返回i>0;
IL_0001:ldarg.0
IL_0002:ldc.i4.0
IL_0003:cgt
IL_0005:stloc.0
//(无C#代码)
IL_0006:br.s IL_0008
IL_0008:ldloc.0
IL_0009:ret
}//方法结束程序::'g_uIsNumberValid | 30_0'

一次。编译器生成一个静态方法来表示这一点。即使该方法必须关闭外部声明的变量,编译器也会通过引用传递它们。@JohnathanBarclay:动作或函数委托如何,编译器对它们的处理是否相同?换句话说,编译器处理嵌套函数和委托的方式是否不同?委托是引用类型,并且被实例化/放在堆上,或者您是指匿名函数(例如lambda表达式)?@JohnathanBarclay:是的,我的意思是lambda表达式,而不是完全的委托。不,对于匿名函数,编译器使用表示该函数的方法生成一个类。在函数不需要闭包的情况下,编译器可以将生成的类优化为单例,否则每次调用该方法时都会创建该类的新实例。从技术上讲,这意味着它将成为其类的一个成员,对吗?@ArnoldZahrneinder Yes,它是封闭类的静态成员。这很有趣,因为在编译时,我们无法通过它的类直接获取对该函数的引用,但是如果动态运行代码,例如使用反射,这是可能的。从技术上讲,您可以这样做,但是实现是未定义的,您不能依赖于它的工作。
>>>>>>>>>>
        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'