转自:老胡写代码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链表并依次执行它们。看看结果

相信很多人看到这个结果的表情是这样的!!难道不应该是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;}

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

除此之外,还有一个修复的方法,在创建闭包的时候,使用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#异步编程看这篇就够了

给我好看

您看此文用

  · 

秒,转发只需1秒呦~

好看你就

点点

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

  1. 从编译器层面理解C#中的闭包的这个坑!

    前言 在公众号上看到一篇文章<正确使用和理解C#中的闭包>,里面提到了闭包的一个坑: 当捕获的外部变量为for循环的迭代变量时,C#认为变量i是定义在循环体外的.所以,当添加委托集合的fo ...

  2. openstack连通性检查显示验证失败_SLEEP:睡眠周期和年龄中的EEG连通性

    研究目标: 在年轻人中,睡眠与非快速眼动(NREM)睡眠与第一个周期中大脑连通性的重要变化有关.本研究旨在评估睡眠中的EEG连通性在年轻人和老年人之间以及在整个睡眠周期中的差异. 方法: 我们使用虚部 ...

  3. js中的闭包问题(持续更新)

    闭包,是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 好啦说人话: "我的理解是,闭包就是能够读取其他函数内部变量的函数." ...

  4. Groovy中的闭包

    groovy中的一个核心语法:closurs,也叫闭包.闭包在groovy中是一个处于代码上下文中的开放的,匿名代码块.它可以访问到其外部的变量或方法. 1. 句法 1.1 定义一个闭包 { [clo ...

  5. 在 Swift 中使用闭包实现懒加载

    本文讲的是在 Swift 中使用闭包实现懒加载, 原文地址:Swift Lazy Initialization with Closures 原文作者:Bob Lee 译文出自:掘金翻译计划 译者:ls ...

  6. 初步学习JS中的闭包

    JS高级程序设计(3rd)中对闭包的定义就是一句话,首先闭包是一个函数,怎样的函数呢?有权访问另一个函数作用域中的变量 的函数.而创建闭包的常见方式就是在一个函数的内部创建另一个函数,就是嵌套函数. ...

  7. javascript中的闭包这一篇就够了

    什么是闭包 维基百科中的概念 在计算机科学中,闭包(也称词法闭包或函数闭包)是指一个函数或函数的引用,与一个引用环境绑定在一起,这个引用环境是一个存储该函数每个非局部变量(也叫自由变量)的表. 闭包, ...

  8. 避免在Swift Struct中使用闭包

    为什么我们应该避免在结构体使用闭包 我们所有人都喜欢闭包,你难道不喜欢吗? Closure能够让iOS开发者生活更轻松.如果它让我们更轻松了,那为啥我还要说不在在结构体中使用闭包了,原因就是:&quo ...

  9. 两个函数彻底理解Lua中的闭包

    本文通过两个函数彻底搞懂Lua中的闭包,相信看完这两个函数,应该能理解什么是Lua闭包.废话不多说,上 code: 1 --[[************************************ ...

最新文章

  1. 中国电磁线行业供需状况与十四五竞争态势研究报告2022版
  2. python3.X 使用pip 离线安装whl包(转载)
  3. EF架构~codeFirst从初始化到数据库迁移
  4. pap与chap协议
  5. 火星人谚语系列之五:答案将由我在下一分钟给出(心想事成)
  6. L3-008. 喊山-PAT团体程序设计天梯赛GPLT(广度优先搜索)
  7. FFmpeg简介及常见用法
  8. 判断元素是否为HTMLElement元素
  9. linux中括号 美元符号怎么打,键盘输入美元符号
  10. 使用fiddler代理,手机无法上网
  11. [枚举] COGS 1580 [WC2005]友好的生物
  12. 【Python网络爬虫实战篇】使用selenium+requests爬取下载高清源视频:关于爬取m3u8文件链接解析为ts视频合并成mp4视频的分析实战
  13. 【Unity Shader】(2)半兰伯特模型 构建光照
  14. 福特sync恢复出厂设置_急急急啊!福特sync服务中心如何注册
  15. 不会被汗水浸湿的T恤,让跑步健身更清爽
  16. Smart Package Robot,高级安装记录器和 RPA 脚本系统
  17. python常用英文缩写_python常用英文单词
  18. 腾讯广告/shopee招聘
  19. wwise集成到unreal
  20. 信息安全服务资质是什么?

热门文章

  1. MongoRepository findById方法使用示例
  2. centos7中安装JDK8-281版本
  3. 如何查看ddos 放开的ip
  4. spring核心功能包中已经包含了cglib功能
  5. ArrayList集合的使用和源码详细分析
  6. 台式电脑耳机插孔在哪_不到一千元的迷你电脑究竟如何?Intel NUC7CJYH测评来了...
  7. Oracle 原理: 物化视图,快照,实体化视图。
  8. json mysql乱码问题_JSON数据乱码问题
  9. Linux信号列表(非实时信号和实时信号)
  10. 启明云端分享| 基于ESP32-S2模块的彩屏86盒应用,有哪些亮点呢