Lambda表达式与闭包

作者:Chdon 发布时间: 2025-09-10 阅读量:17 评论数:0

一、观察Lambda函数编译生成IL代码。

1. 源代码

internal class Program
{
    static void Main(string[] args)
    {
        Func<int, int, int> add = (a, b) => a + b;
        add(1, 2);
    }
}

2. IL代码

.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	// Method begins at RVA 0x2050
	// Header size: 12
	// Code size: 43 (0x2b)
	.maxstack 3
	.entrypoint
	.locals init (
		[0] class [mscorlib]System.Func`3<int32, int32, int32> 'add'
	)

	// {
	IL_0000: nop
	// Func<int, int, int> func = (int a, int b) => a + b;
	IL_0001: ldsfld class [mscorlib]System.Func`3<int32, int32, int32> ConsoleApp5.Program/'<>c'::'<>9__0_0'
	IL_0006: dup
	IL_0007: brtrue.s IL_0020

	// (no C# code)
	IL_0009: pop
	// func(1, 2);
	IL_000a: ldsfld class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
	IL_000f: ldftn instance int32 ConsoleApp5.Program/'<>c'::'<Main>b__0_0'(int32, int32)
	IL_0015: newobj instance void class [mscorlib]System.Func`3<int32, int32, int32>::.ctor(object, native int)
	IL_001a: dup
	IL_001b: stsfld class [mscorlib]System.Func`3<int32, int32, int32> ConsoleApp5.Program/'<>c'::'<>9__0_0'

	IL_0020: stloc.0
	IL_0021: ldloc.0
	IL_0022: ldc.i4.1
	IL_0023: ldc.i4.2
	IL_0024: callvirt instance !2 class [mscorlib]System.Func`3<int32, int32, int32>::Invoke(!0, !1)
	IL_0029: pop
	// }
	IL_002a: ret
} // end of method Program::Main

.class nested private auto ansi sealed serializable beforefieldinit '<>c'
	extends [mscorlib]System.Object
{
	.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	// Fields
	.field public static initonly class ConsoleApp5.Program/'<>c' '<>9'
	.field public static class [mscorlib]System.Func`3<int32, int32, int32> '<>9__0_0'

	// Methods
	.method private hidebysig specialname rtspecialname static 
		void .cctor () cil managed 
	{
		// Method begins at RVA 0x2090
		// Header size: 1
		// Code size: 11 (0xb)
		.maxstack 8

		// <>9 = new <>c();
		IL_0000: newobj instance void ConsoleApp5.Program/'<>c'::.ctor()
		IL_0005: stsfld class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
		// }
		IL_000a: ret
	} // end of method '<>c'::.cctor

	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x209c
		// Header size: 1
		// Code size: 8 (0x8)
		.maxstack 8

		// {
		IL_0000: ldarg.0
		// (no C# code)
		IL_0001: call instance void [mscorlib]System.Object::.ctor()
		// }
		IL_0006: nop
		IL_0007: ret
	} // end of method '<>c'::.ctor

	.method assembly hidebysig 
		instance int32 '<Main>b__0_0' (
			int32 a,
			int32 b
		) cil managed 
	{
		// Method begins at RVA 0x20a5
		// Header size: 1
		// Code size: 4 (0x4)
		.maxstack 8

		// return a + b;
		IL_0000: ldarg.1
		IL_0001: ldarg.2
		IL_0002: add
		IL_0003: ret
	} // end of method '<>c'::'<Main>b__0_0'

} // end of class <>c

执行步骤:

  • 初始化时,生成一个名称为addFunc<int, int, int>委托的局部变量,序号为0,初始值为null

  • IL_0001~IL_0007: 检查静态字段<>9__0_0是否已缓存委托实例,避免重复创建。如果已创建,则直接跳转到调用部分。

  • IL_0009~IL_001b: 加载lambda编译后的方法地址:<Main>b__0_0(即a + b逻辑),并创建Func<int,int,int>委托实例。

  • IL_0020: stloc.0将委托存入序号为0的局部变量。

  • IL_0021: ldloc.0 读取序号为0的局部变量,即Func<int, int, int>委托。

  • IL_0022~IL_0024: 压入参数a, b,即1和2,并调用add委托。

3. 总结

lambda委托在编译后,会生成一个class,来承载对应的委托方法。内部使用静态字段来缓存委托,减少多次调用性能损耗。

二、观察lambda函数闭包编译生成的IL代码。

1. 源代码

internal class Program
{
    static void Main(string[] args)
    {
        int c = 1;
        Func<int, int, int> add = (a, b) => a + b + c;
        c = 2;
        add(1, 2);
    }
}

运行结果:5

2. IL代码

.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	// Method begins at RVA 0x2050
	// Header size: 12
	// Code size: 44 (0x2c)
	.maxstack 3
	.entrypoint
	.locals init (
		[0] class ConsoleApp5.Program/'<>c__DisplayClass0_0' 'CS$<>8__locals0',
		[1] class [mscorlib]System.Func`3<int32, int32, int32> 'add'
	)

	// {
	IL_0000: newobj instance void ConsoleApp5.Program/'<>c__DisplayClass0_0'::.ctor()
	IL_0005: stloc.0
	// (no C# code)
	IL_0006: nop
	// int c = 1;
	IL_0007: ldloc.0
	IL_0008: ldc.i4.1
	IL_0009: stfld int32 ConsoleApp5.Program/'<>c__DisplayClass0_0'::c
	// Func<int, int, int> func = (int a, int b) => a + b + c;
	IL_000e: ldloc.0
	IL_000f: ldftn instance int32 ConsoleApp5.Program/'<>c__DisplayClass0_0'::'<Main>b__0'(int32, int32)
	IL_0015: newobj instance void class [mscorlib]System.Func`3<int32, int32, int32>::.ctor(object, native int)
	IL_001a: stloc.1
	// c = 2;
	IL_001b: ldloc.0
	IL_001c: ldc.i4.2
	IL_001d: stfld int32 ConsoleApp5.Program/'<>c__DisplayClass0_0'::c
	// func(1, 2);
	IL_0022: ldloc.1
	IL_0023: ldc.i4.1
	IL_0024: ldc.i4.2
	IL_0025: callvirt instance !2 class [mscorlib]System.Func`3<int32, int32, int32>::Invoke(!0, !1)
	IL_002a: pop
	// }
	IL_002b: ret
} // end of method Program::Main

.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0'
	extends [mscorlib]System.Object
{
	.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	// Fields
	.field public int32 c

	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x2091
		// Header size: 1
		// Code size: 8 (0x8)
		.maxstack 8

		// {
		IL_0000: ldarg.0
		// (no C# code)
		IL_0001: call instance void [mscorlib]System.Object::.ctor()
		// }
		IL_0006: nop
		IL_0007: ret
	} // end of method '<>c__DisplayClass0_0'::.ctor

	.method assembly hidebysig 
		instance int32 '<Main>b__0' (
			int32 a,
			int32 b
		) cil managed 
	{
		// Method begins at RVA 0x209a
		// Header size: 1
		// Code size: 11 (0xb)
		.maxstack 8

		// return a + b + c;
		IL_0000: ldarg.1
		IL_0001: ldarg.2
		IL_0002: add
		IL_0003: ldarg.0
		IL_0004: ldfld int32 ConsoleApp5.Program/'<>c__DisplayClass0_0'::c
		IL_0009: add
		IL_000a: ret
	} // end of method '<>c__DisplayClass0_0'::'<Main>b__0'

} // end of class <>c__DisplayClass0_0

执行步骤与之前类似,主要有几点区别:

  • 生成的委托类'<>c__DisplayClass0_0' 中,变量c作为字段被存储在类中。

  • IL_0007~IL_0009: int c = 1; 将数字1 存储在局部变量(委托实例)'CS$<>8__locals0' 的字段c中。

  • IL_001b~IL_001d: c = 2; 将数字2 存储在局部变量(委托实例)'CS$<>8__locals0' 的字段c中。

  • 使用类中字段c 参与函数add计算。

3. 总结

局部变量闭包,会作为委托实例的字段,随实例一同保存。局部变量变更,即同时变更委托实例字段中值。

评论