C# 闭包,闭包原理

简单的闭包

private static void Main(string[] args)
{
	Func<int> a = getFun1();
	a(); //输出1
	a(); //输出2
}

private static Func<int> getFun1()
{	
	int b = 1;
	return delegate
	{
		Console.WriteLine(b);
		return b++;
	};
}

使用ILSpy打开编译后的文件,C#选择1.0 / VS.NET,可以看到如下:

private sealed class <>c__DisplayClass1_0
{
	public int b;
	internal int <getFun1>b__0()
	{
		Console.WriteLine(b);
		return b++;
	}
}

private static void Main(string[] args)
{
	Func<int> a = getFun1();
	a();
	a();
}

private static Func<int> getFun1()
{
	<>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0();
	<>c__DisplayClass1_.b = 1;
	return new Func<int>(<>c__DisplayClass1_.<getFun1>b__0);
}

名称有点怪异,不过不太影响阅读(再怪还不是一个名称标识)。
细细看很简单,不解析。

ILSpy 的选择C# 语言的版本很好用,有兴趣可以打开使用async await 的文件,可以看到async的实现原理。

<think>我们正在讨论C#闭包的工作原理及简单解释。根据提供的引用资料,闭包是指一个函数(如匿名方法、lambda表达式)能够捕获并访问其外部作用域中的变量,即使这些变量在原始作用域中已经不再存在。在C#中,闭包通过编译器生成一个隐藏的类来实现,该类将捕获的变量作为其成员字段,从而延长这些变量的生命周期。简单来说,当你在一个方法内部定义一个lambda表达式或匿名方法,并且这个表达式或方法引用了方法中的局部变量时,编译器会生成一个类,这个类包含这些被引用的变量作为字段。然后,该lambda表达式或方法会变成这个类的一个实例方法,而该实例会被传递到需要的地方(例如委托)。这样,即使外部方法已经执行完毕,只要这个委托实例还存在,被捕获的变量就不会被垃圾回收,因为它们现在属于这个生成的类的实例。根据引用[5]:编译器生成了一个内部类,把临时变量转换为成员变量,这就是编译器生成的"闭包"。Func委托内部维持了对这个闭包类实例的引用,所以只要委托存在,这个变量就不会被回收。现在,我们用一个简单的例子来说明(参考引用[1]和[2]):假设我们有以下代码:publicActionCreateCounter(){intcount=0;return()=>{count++;Console.WriteLine(count);};}调用CreateCounter方法时,它返回一个Action委托。在这个委托中,我们使用了外部变量count。编译器会生成一个类似于下面的类:privateclassClosureClass{publicintcount;publicvoidAnonymousMethod(){count++;Console.WriteLine(count);}}然后在CreateCounter方法中,实际上会这样:publicActionCreateCounter(){ClosureClassclosure=newClosureClass();closure.count=0;returnnewAction(closure.AnonymousMethod);}因此,当我们调用返回的委托时,实际上是在调用closure实例的AnonymousMethod,而count变量现在作为closure实例的一个字段存在。所以多次调用该委托会看到count递增。另外,引用[4]提到:在C#中,lambda表达式捕获值类型时,会将其“提升”到堆上(通过闭包类),而不是留在栈上。这样,即使创建变量的方法已经返回,变量仍然存在。总结:C#闭包的工作原理是编译器自动生成一个类来封装被捕获的变量,并将匿名函数转换为该类的方法。这样,被捕获的变量生命周期就被延长,与闭包函数(委托)的生命周期一致。根据要求,生成相关问题:1.C#闭包中捕获的变量是值类型和引用类型有什么区别?2.闭包会导致内存泄漏吗?如何避免?3.除了lambda表达式,C#中还有哪些方式可以形成闭包?4.在C#中,闭包是如何影响变量作用域的?5.如何查看编译器生成的闭包类?</think>### C#闭包的原理及工作原理 闭包是指**函数能够捕获并访问其外部作用域中的变量**,即使这些变量在原始作用域中已超出生命周期。在C#中,闭包通过编译器生成的隐藏类实现,本质上是将局部变量"提升"为类的成员字段,从而延长其生命周期。 #### 工作原理(分步解析) 1. **变量捕获** 当lambda表达式或匿名方法访问外部变量时,编译器自动创建一个隐藏类(闭包类)。 ```csharp public Action CreateCounter() { int count = 0; // 局部变量 return () => { count++; Console.WriteLine(count); }; } ``` > 编译器生成类似以下类[^1][^5]: > ```csharp > private sealed class ClosureClass > { > public int count; // 捕获的变量转为成员字段 > public void AnonymousMethod() => count++; > } > ``` 2. **生命周期延长** 被捕获的变量(如`count`)从栈内存移至堆内存(闭包类的实例),其生命周期与委托实例绑定。 ```csharp var counter = CreateCounter(); counter(); // 输出1(闭包实例未被回收) counter(); // 输出2(count值持续保留) ``` 3. **值修改同步** 闭包内对外部变量的修改会同步到闭包类的字段中: ```csharp int y = 5; Func<int, int> addY = x => x + y; y = 10; // 修改外部变量 Console.WriteLine(addY(1)); // 输出11(非6)[^3][^4] ``` #### 关键特性 - **值类型提升**:即使捕获`int`等值类型,也会被移至堆内存[^4]。 - **延迟绑定**:访问的是变量最终值,而非声明时的值(如上述`y`的修改)[^4]。 - **内存管理**:闭包类实例由委托引用,只要委托存活,闭包实例就不会被GC回收[^5]。 #### 简单解释 > **闭包 = 函数 + 被捕获的上下文环境**。在C#中,它是编译器通过生成隐藏类实现的语法糖,使函数能"记住"并操作其定义作用域中的变量。 --- ### 相关问题 1. C#闭包中捕获值类型和引用类型有何区别? 2. 如何避免闭包导致的内存泄漏问题? 3. 除了lambda表达式,C#中还有哪些方式会形成闭包? 4. 闭包在异步编程中可能引发哪些陷阱? 5. 如何通过ILDASM查看编译器生成的闭包类? [^1]: 闭包通过隐藏类捕获变量 [^3]: 闭包访问外部变量最终值 [^4]: 值类型提升至堆内存 [^5]: 闭包实例与委托生命周期绑定
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zerolook

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值