c# 睡眠3秒_C#中的闭包和意想不到的坑

04ab3a5480965498b69f574fd68f9efd.gif

转自:老胡写代码 cnblogs.com/deatharthas/p/13166987.html

虽然闭包主要是函数式编程的玩意儿,而C#的最主要特征是面向对象,但是利用委托或lambda表达式,C#也可以写出具有函数式编程风味的代码。

同样的,使用委托或者lambda表达式,也可以在C#中使用闭包。

根据WIKI的定义,闭包又称语法闭包或函数闭包,是在函数式编程语言中实现语法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。闭包也可以延迟变量的生存周期。

看定义好像有点迷糊,让我们看看下面的例子

class Program
{
static Action CreateGreeting(string message)
{
return () => { Console.WriteLine("Hello " + message); };
}
static void Main()
{
Action action = CreateGreeting("DeathArthas");
action();
}
}

这个例子非常简单,用lambda表达式创建一个Action对象,之后再调用这个Action对象。

但是仔细观察会发现,当Action对象被调用的时候,CreateGreeting方法已经返回了,作为它的实参的message应该已经被销毁了,那么为什么我们在调用Action对象的时候,还是能够得到正确的结果呢?

原来奥秘就在于,这里形成了闭包。虽然CreateGreeting已经返回了,但是它的局部变量被返回的lambda表达式所捕获,延迟了其生命周期。怎么样,这样再回头看闭包定义,是不是更清楚了一些?

闭包就是这么简单,其实我们经常都在使用,只是有时候我们都不自知而已。比如大家肯定都写过类似下面的代码。

void AddControlClickLogger(Control control, string message)
{
control.Click += delegate
{
Console.WriteLine("Control clicked: {0}", message);
}
}

这里的代码其实就用了闭包,因为我们可以肯定,在control被点击的时候,这个message早就超过了它的声明周期。合理使用闭包,可以确保我们写出在空间和时间上面解耦的委托。

不过在使用闭包的时候,要注意一个陷阱。因为闭包会延迟局部变量的生命周期,在某些情况下程序产生的结果会和预想的不一样。让我们看看下面的例子。

class Program
{
static ListCreateActions()
{
var result = new List();for(int i = 0; i < 5; i++)
{
result.Add(() => Console.WriteLine(i));
}return result;
}static void Main()
{
var actions = CreateActions();for(int i = 0;i {
actions[i]();
}
}
}

这个例子也非常简单,创建一个Action链表并依次执行它们。看看结果

cb8d459d1e098587504644560d2febd9.png

相信很多人看到这个结果的表情是这样的!!难道不应该是0,1,2,3,4吗?出了什么问题?

刨根问底,这儿的问题还是出现在闭包的本质上面,作为“闭包延迟了变量的生命周期”这个硬币的另外一面,是一个变量可能在不经意间被多个闭包所引用。

在这个例子里面,局部变量i同时被5个闭包引用,这5个闭包共享i,所以最后他们打印出来的值是一样的,都是i最后退出循环时候的值5。

要想解决这个问题也很简单,多声明一个局部变量,让各个闭包引用自己的局部变量就可以了。

//其他都保持与之前一致
static ListCreateActions()
{var result = new List();for (int i = 0; i < 5; i++)
{int temp = i; //添加局部变量
result.Add(() => Console.WriteLine(temp));
}return result;
}

930cbacfdd483ae67a45ae84a439cb8f.png

这样各个闭包引用不同的局部变量,刚刚的问题就解决了。

除此之外,还有一个修复的方法,在创建闭包的时候,使用foreach而不是for。至少在C# 7.0 的版本上面,这个问题已经被注意到了,使用foreach的时候编译器会自动生成代码绕过这个闭包陷阱。

//这样fix也是可以的
static List CreateActions()
{var result = new List();foreach (var i in Enumerable.Range(0,5))
{
result.Add(() => Console.WriteLine(i));
}return result;
}

这就是在闭包在C#中的使用和其使用中的一个小陷阱,希望大家能通过老胡的文章了解到这个知识点并且在开发中少走弯路!

往期 精彩 回顾

【推荐】.NET Core开发实战视频课程 ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了

给我好看

6536bd8b56c34a237e6e260dcab1a892.png

您看此文用

 8b1c8e1c6cfc8e39b981657a85f5157a.gif 5ff67ea3e676051dca9eca40c53c3e5e.gif·9101dc8deb2597692eac9796904cc9cc.gif 7bf2422ea3268a9f8a8be6a3f053b17c.gif

秒,转发只需1秒呦~

03f334a1c40348d15f2a2c5563d324b1.png

好看你就

点点

d575a27ed873352c2721e7c4d721ef0c.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#闭包是指一个函数能够访问它外部的变量,即使这些变量在函数外部定义。闭包可以在函数内部创建,也可以作为函数的返回值返回。 下面是一个简单的闭包示例: ``` public Func<int, int> Add(int a) { return b => a + b; } // 调用 Func<int, int> add5 = Add(5); int result = add5(3); // result = 8 ``` 在上面的代码,Add方法返回一个匿名函数,这个匿名函数能够访问Add方法的变量a。当我们调用Add(5)时,会返回一个函数,这个函数会将传入的参数b加上a,然后返回结果。 关于foreach闭包,需要注意的是,foreach循环变量会在每次迭代重新赋值,因此如果在循环体内部使用匿名函数访问循环变量,可能会导致意外的结果。例如: ``` List<Func<int>> funcs = new List<Func<int>>(); int[] nums = { 1, 2, 3 }; foreach (int num in nums) { funcs.Add(() => num); } foreach (Func<int> func in funcs) { Console.WriteLine(func()); } ``` 上面的代码,我们在第一个foreach循环向一个List添加了三个匿名函数,这些匿名函数都返回循环变量num。在第二个foreach循环,我们依次调用了这三个函数,期望的结果是输出1、2、3。但实际上,输出的结果都是3,这是因为在第一个foreach循环,循环变量num在每次迭代都被重新赋值,而匿名函数访问的是循环变量的引用,最终所有的函数都返回了最后一次迭代的值3。 为了避免这种问题,我们可以在循环体内部创建一个局部变量来保存循环变量的值,然后在匿名函数访问这个局部变量,例如: ``` List<Func<int>> funcs = new List<Func<int>>(); int[] nums = { 1, 2, 3 }; foreach (int num in nums) { int temp = num; funcs.Add(() => temp); } foreach (Func<int> func in funcs) { Console.WriteLine(func()); } ``` 这样就可以输出期望的结果了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值