正确使用和理解C#中的闭包

本文深入探讨了C#中的闭包和捕获变量概念,通过实例展示了如何在Lambda表达式中捕获外部变量,并解释了为何在for循环中捕获迭代变量会导致预期之外的结果。此外,还分析了C#编译器生成的IL代码,揭示了闭包背后的实现原理。最后,提到了foreach循环在不同版本中对捕获变量的影响。
摘要由CSDN通过智能技术生成

定义

我们把在Lambda表达式(或匿名方法)中所引用的外部变量称为捕获变量。而捕获变量的表达式就称为闭包

捕获变量

捕获的变量会在真正调用委托时“赋值”,而不是在捕获时“赋值”,即总是使用捕获变量的最新的值。如下代码所示,调用委托时,age的最新值为30,所以输出的值也是30。

int age = 28;
//定义委托
Func<int, string> consoleAge = i => $"洋小豆今年{i}岁了";
age = 30;
//调用委托
string outputMsg = consoleAge(age);
outputMsg.Dump();

输出结果如下图:a91727a1b7daa4b45a95c544b2123532.png

捕获迭代变量

当捕获的外部变量为for循环的迭代变量时,C#认为变量i是定义在循环体外的。所以,当添加委托集合的for循环执行完时,i的值已经变为3了;因此,我们在foreach中循环调用委托时,i的值就都是3了。

List<Action> levyActions = new List<Action>();
for (int i = 0; i < 3; i++)
{
   levyActions.Add(()=> i.Dump());
}
foreach (Action action in levyActions)
{
   action();
}

输出结果如下图:b074cd0c1eb556035223669c7c4b9dfd.png

那么,如果我们期望输出的结果为1,2,3那需要怎么修改呢?这里我们只需要在for循环内部使用局部变量即可(每次循环捕获的是不同的变量),如下修改后的代码:

for (int i = 0; i < 3; i++)
{
   int tmp = i;
   levyActions.Add(()=> tmp.Dump());
}

输出结果如下图:

d419bd0560cf2f09568dd172e5dfb747.png

看到这里大家应该基本明白怎么回事了吧!再想下,迭代除了可以使用for还可以使用foreach啊!那么,我们把上面示例中的for循环部分改造成foreach会怎么样呢?

string[] names = new string[] {"洋小豆", "列位一分钟", "levy"};
List<Action> levyActions = new List<Action>();
foreach (string name in names)
{
   levyActions.Add(()=> name.Dump());
}

输出结果如下图:

86e1dbee8644f6e4011034fbf9e0cd7e.png

纳尼?输出的结果竟然跟我们上面的讲解不一样?不是应该输出捕获变量的最新值吗?应该输出3个“levy”啊!哈哈,这里是因为我的示例代码是基于.net core3.0的,从C#5.0开始,foreach认为循环变量都应该是“新”的变量。所以,每次循环中创建委托时捕获的变量都不是同一个变量。因而,输出的值肯定也就不一样了。有兴趣的童鞋可以在C#5.0之前的版本下测试下,看看输出的是不是3个“levy”。

背后原理

分析IL代码我们可以得知,编译器在背后生成了一个私有的密封类c__DisplayClass4_0,它将外部变量包装成类的成员变量,而委托方法包装成类的方法。所以,上述捕获for迭代变量的示例代码就可以修改成如下:

void Main()
{
 List<Action> levyActions = new List<Action>();
 c__DisplayClass4_0 local = new c__DisplayClass4_0();
 for (local.i = 0; local.i < 3; local.i++)
 {
    levyActions.Add(() => local.Main_b__0());
 }
 foreach (Action action in levyActions)
 {
    action();
 }
}

private sealed class c__DisplayClass4_0
{
 public int i;
 internal void Main_b__0()
 {
    i.Dump();
 }
}

涉及到外部变量i的地方都替换成了local.i,所以,每次循环修改的始终是c__DisplayClass4_0的成员变量i的值,那么调用委托时输出的自然都是3了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值