本地函数的代码创建了多少次?只调用一次或任何时候调用父函数?
How many times the code of a local function is created? Only once or anytime the parent function is called?
现代 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 代码。
正如我们所见,本地方法被转换为像当前的任何实例方法一样class并且代码只存在一次。
因此编译器“重构”了自己。
C# 本地方法不像 C 和 C++ inline functions 代码根据用法重复。
本地方法允许重构代码并创建更干净的代码,同时不允许从包含它们的方法的外部进行调用,以隔离处理 and/or 以多次调用它们。
以前也可以使用 Action and Func 的本地 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
这里是本地方法,现在是 class 方法:
.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'
现代 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 代码。
正如我们所见,本地方法被转换为像当前的任何实例方法一样class并且代码只存在一次。
因此编译器“重构”了自己。
C# 本地方法不像 C 和 C++ inline functions 代码根据用法重复。
本地方法允许重构代码并创建更干净的代码,同时不允许从包含它们的方法的外部进行调用,以隔离处理 and/or 以多次调用它们。
以前也可以使用 Action and Func 的本地 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
这里是本地方法,现在是 class 方法:
.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'