理解C#中的闭包

1、 闭包的含义

首先闭包并不是针对某一特定语言的概念,而是一个通用的概念。除了在各个支持函数式编程的语言中,我们会接触到它。一些不支持函数式编程的语言中也能支持闭包(如java8之前的匿名内部类)。

在看过的对于闭包的定义中,个人觉得比较清晰的是在《JavaScript高级程序设计》这本书中看到的。具体定义如下:

闭包是指有权访问另一个函数作用域中的变量的函数。

注意,闭包这个词本身指的是一种函数。而创建这种特殊函数的一种常见方式是在一个函数中创建另一个函数。

2、 在C# 中使用闭包(例子选取自《C#函数式程序设计》)

下面我们通过一个简单的例子来理解C#闭包

Copyclass Program
{static void Main(string[] args){Console.WriteLine(GetClosureFunction()(30));}static Func<int, int> GetClosureFunction(){int val = 10;Func<int, int> internalAdd = x => x + val;Console.WriteLine(internalAdd(10));val = 30;Console.WriteLine(internalAdd(10));return internalAdd;}
}

上述代码的执行流程是Main函数调用GetClosureFunction函数,GetClosureFunction返回了委托internalAdd并被立即执行了。

输出结果依次为20、40、60

对应到一开始提出的闭包的概念。这个委托internalAdd就是一个闭包,引用了外部函数GetClosureFunction作用域中的变量val。

注意:internalAdd有没有被当做返回值和闭包的定义无关。就算它没有被返回到外部,它依旧是个闭包。

3、 理解闭包的实现原理

我们来分析一下这段代码的执行过程。在一开始,函数GetClosureFunction内定义了一个局部变量val和一个利用lamdba语法糖创建的委托internalAdd。

第一次执行委托internalAdd 10 + 10 输出20

接着改变了被internalAdd引用的局部变量值val,再次以相同的参数执行委托,输出40。显然局部变量的改变影响到了委托的执行结果。

GetClosureFunction将internalAdd返回至外部,以30作为参数,去执行得到的结果是60,和val局部变量最后的值30是一致的。

val 作为一个局部变量。它的生命周期本应该在GetClosureFunction执行完毕后就结束了。为什么还会对之后的结果产生影响呢?

我们可以通过反编译来看下编译器为我们做的事情。

为了增加可读性,下面的代码对编译器生成的名字进行修改,并对代码进行了适当的整理。


class Program
{sealed class DisplayClass{public int val;public int AnonymousFunction(int x){return x + this.val;}}static void Main(string[] args){Console.WriteLine(GetClosureFunction()(30));}static Func<int, int> GetClosureFunction(){DisplayClass displayClass = new DisplayClass();displayClass.val = 10;Func<int, int> internalAdd = displayClass.AnonymousFunction;Console.WriteLine(internalAdd(10));displayClass.val = 30;Console.WriteLine(internalAdd(10));return internalAdd;}
}

编译器创建了一个匿名类(如果不需要创建闭包,匿名函数只会是与GetClosureFunction生存在同一个类中,并且委托实例会被缓存,参见clr via C# 第四版362页),并在GetClosureFunction中创建了它实例。局部变量实际上是作为匿名类中的字段存在的。

4、 C#7对于不作为返回值的闭包的优化

如果在vs2017中编写第二节的代码。会得到一个提示,询问是否把lambda表达式(匿名函数)托转为本地函数。本地函数是c#7提供的一个新语法。那么使用本地函数实现闭包又会有什么区别呢?

如果还是第二节那样的代码,改成本地函数,查看IL代码。实际上不会发生任何变化。


class Program
{static void Main(string[] args){Console.WriteLine(GetClosureFunction()(30));}static Func<int, int> GetClosureFunction(){int val = 10;int InternalAdd(int x) => x + val;Console.WriteLine(InternalAdd(10));val = 30;Console.WriteLine(InternalAdd(10));return InternalAdd;}
}

但是当internalAdd不需要被返回时,结果就不一样了。

下面分别来看下匿名函数和本地函数创建不作为返回值的闭包的时候演示代码及经整理的反编译代码。

匿名函数


static void GetClosureFunction()
{int val = 10;Func<int, int> internalAdd = x => x + val;Console.WriteLine(internalAdd(10));val = 30;Console.WriteLine(internalAdd(10));
}

经整理的反编译代码

sealed class DisplayClass
{public int val;public int AnonymousFunction(int x){return x + this.val;}
}static void GetClosureFunction()
{DisplayClass displayClass = new DisplayClass();displayClass.val = 10;Func<int, int> internalAdd = displayClass.AnonymousFunction;Console.WriteLine(internalAdd(10));displayClass.val = 30;Console.WriteLine(internalAdd(10));
}

本地函数


class Program
{static void Main(string[] args){}static void GetClosureFunction(){int val = 10;int InternalAdd(int x) => x + val;Console.WriteLine(InternalAdd(10));val = 30;Console.WriteLine(InternalAdd(10));}
}

经整理的反编译代码


// 变化点1:由原来的class改为了struct
struct DisplayClass
{public int val;public int AnonymousFunction(int x){return x + this.val;}
}static void GetClosureFunction()
{DisplayClass displayClass = new DisplayClass();displayClass.val = 10;// 变化点2:不再构建委托实例,直接调用值类型的实例方法Console.WriteLine(displayClass.AnonymousFunction(10));displayClass.val = 30;Console.WriteLine(displayClass.AnonymousFunction(10));
}

上述这两点变化在一定程度上能够带来性能的提升,目前的理解是,用结构体代替类,结构体实例能够在方法跑完后就立即释放,不需要等待垃圾回收,所以在官方的推荐中,如果委托的使用不是必要的,更推荐使用本地函数而非匿名函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306360.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux select读取节点数据失败_MySQL中覆盖索引查询和select*查询执行结果案例分析...

索引优化建议在MySQL中要尽可能使用覆盖索引进行检索&#xff0c;只访问索引的查询(索引列和查询列一致)&#xff0c;减少select * 可提高查询效率覆盖索引(Covering Index)理解方式一:就是select的数据列只用从索引中就能够取得&#xff0c;不必读取数据行&#xff0c;MySQL可…

机器学习之乳腺癌预测

机器学习之乳腺癌预测 (一)问题分析1.问题背景2.问题分析3.题目所需的代码及数据(二).导入数据1. 认识数据集2.导入数据3.数据概述(三)EDA 数据探索性分析1.描述性统计分析A.查看数据维度(行列数)B.数据统计描述(列名对应的信息)C.查看数据信息(统计学信息)D.缺失处理2.数据可视…

使用 Azure WAF 羞辱黑客的智商

点击上方蓝字关注“汪宇杰博客”导语还记得之前给大家介绍过的《使用 Azure Web 应用防火墙拦截黑客攻击》吗&#xff1f;今天我又带来了一个有趣的 Azure WAF 小技巧&#xff0c;可以让你爽一把。好奇的黑客今天 Azure Application Insights 上发现了一段集中时间的404错误&am…

实现option上下移动_用jQuery实现lt;selectgt;选项上下移动 - 不要哀求 学会争取 若是如此 终有所获 - ITeye博客...

嘖嘖嘖&#xff0c;短短兩個API串接: $opt.next().after($opt)就做出了向下移動的效果。記得以往用純Javascript寫&#xff0c;還得判斷是否為最後一個&#xff0c;若是就不能下移&#xff1b;然後上下位置交換得用options[index]搞半天。不得不要再次讚嘆jQuery的神奇!$(funct…

机器学习之乳腺癌问题(SVM)

机器学习之乳腺癌问题SVM题目所需的代码及数据利用SVM建模SVM调参题目所需的代码及数据 链接&#xff1a;https://pan.baidu.com/s/1bS7Ku_PUfcimiVkmLz9Fzw 提取码&#xff1a;0929 利用SVM建模 import matplotlib.pyplot as plt import pandas as pd import numpy as npfro…

数据科学与python语言实验——NumPy数值计算基础

NumPy数值计算基础实验数据&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1-E2ShVTdI0X5lwDtMLFFsQ 提取码&#xff1a;0929 代码实现&#xff1a; 之前不会的地方&#xff1a; 1&#xff0e;读取文件 使用numpy内置的loadtxt()函数以及这个函数的参数frame&#x…

如何将日志记录到 Windows事件日志 中

每当出现一些未捕获异常时&#xff0c;操作系统都会将异常信息写入到 Windows 事件日志 中&#xff0c;可以通过 Windows 事件查看器 查看&#xff0c;如下图&#xff1a;这篇文章将会讨论如何使用编程的方式将日志记录到 Windows 事件日志 中。安装 EventLog 要想在 .NET Core…

linux磁盘写保护怎么修改_mount: /dev/vdb 写保护,将以只读方式挂载

今天再mount磁盘的时候遇到一个问题&#xff1a;[rootoracle1 /]# mount /dev/vdb /oradatamount: /dev/vdb 写保护&#xff0c;将以只读方式挂载mount: 未知的文件系统类型“(null)”这个问题重新格式化磁盘后就好了mkfs.ext4 /dev/vdb让这个盘在系统启动的时候自动挂载[oracl…

【Java】springboot

文章目录 Spingboot1、起步依赖2、构建springboot工程jar包3、springboot配置文件4、多环境配置5、maven和boot多环境兼容问题6、配置文件分类7、springboot整合mybatis Spingboot springboot用来简化spring的初始搭建以及开发过程。 比方说&#xff0c;创建一个springmvc程序…

数据科学与python语言——Matplotlib数据可视化基础

Matplotlib数据可视化基础一.读取数据与数据处理阶段1.提取指定行中的数据2.得到>指定数值的数据3.得到指定值得数据4.整体的数据处理&#xff1a;二.画图函数1.plt.subplots()2.plt.subplots_adjust()3.设置x轴y轴的刻度和标签4.使用中文标题在作图时三.画折线图(plot)四.画…

2021年,Azure云遇到. NET5,注定开启高光时刻,微软的心,真大!

云开发诞生的市场背景云开发是一个已经存在了很多年的概念&#xff0c;但在过去未能真正成为主流。然而&#xff0c;由于云和软件即服务的宏观趋势的结合&#xff0c;以及技术的进步&#xff0c;如容器技术 Docker 和 Kubernetes&#xff0c;云开发现在有机会最终成为基于云的应…

fpga开发教程 labview_LabVIEW项目样例 - NI VST FPGA软件编程使用步骤与实例教程

3. LabVIEW项目样例NI VST仪器设计库的能力强大&#xff0c;但是并不能单枪匹马地完全满足软件设计仪器实现默认功能的要求&#xff0c;也不能提供大多数VSA和VSG仪器编程人员所熟悉的简单主机接口。LabVIEW 2012提供了一个新功能&#xff0c;这种方便的机制不仅能够分配附加代…

.net5+nacos+ocelot 配置中心和服务发现实现

相关文章&#xff1a;手动造轮子——为Ocelot集成Nacos注册中心出处&#xff1a;https://www.cnblogs.com/buruainiaaaa/p/14121176.html作者&#xff1a;唐 最近一段时间 因公司业务需要&#xff0c;需要使用.net5做一套微服务的接口&#xff0c;使用nacos 做注册中心和配置中…

数据科学与python语言——Pandas统计分析基础(时间转换+聚合)

Pandas统计分析基础&#xff08;时间转换聚合&#xff09;实验要求一实验二要求全部代码实验要求一 #M表的时间戳类型转为datetime data_Mete[TIMESTAMP]pd.to_datetime(data_Mete[TIMESTAMP],format%Y%m%d%H%M%S)data_VI[Date]pd.to_datetime(data_VI[Date],format%Y/%m/%d) p…

用keil怎么擦除_分享STM32 FLASH 擦除(以及防止误擦除程序代码)、写入

编译环境&#xff1a;我用的是(Keil)MDK4.7.2stm32库版本&#xff1a;我用的是3.5.0一、本文不对FLASH的基础知识做详细的介绍&#xff0c;不懂得地方请查阅有关资料。对STM32 内部FLASH进行编程操作&#xff0c;需要遵循以下流程&#xff1a;FLASH解锁清除相关标志位擦除FLASH…

如何在 ASP.NET Core 中使用 Quartz.NET 执行任务调度

当我们在web开发中&#xff0c;常常会遇到这么一个需求&#xff0c;在后台执行某一项具体的任务&#xff0c;具体的说就是这些任务必须在后台定时执行。Quartz.NET 是一个开源的 JAVA 移植版&#xff0c;它有着悠久的历史并且提供了强大的 Cron 表达式&#xff0c;这篇我们就来…

数据科学与python——Pandas统计分析基础(数据堆叠+数据清洗)

Pandas统计分析基础数据堆叠数据清洗一.合并数据&#xff1a;获取完整的数据集。1.读取数据2.将两个csv文件按照mete.csv文件的日期对齐3.纵向合并数据data1与data24.使用drop_duplicates()函数去除重复值二.异常值处理&#xff1a;去除data3中GPP中的异常点1.根据3σ原则检测异…

python 为什么动态语言图片_聊聊动态语言那些事(Python)

动态编程语言是高级程序设计语言的一个类别&#xff0c;在计算机科学领域已被广泛应用。它是一类在运行时可以改变其结构的语言&#xff1a;例如新的函数、对象、甚至代码可以被引进&#xff0c;已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力&#xff0…

容器的那点事

当我们的后端服务器不够用的时候&#xff0c;我们可以通过容器技术&#xff0c;可以快速的把这些服务器全部虚拟出来&#xff0c; 当然这个虚拟跟虚拟机是不一样的&#xff0c;比虚拟机的方式快多了&#xff0c;早期阿里的淘宝平台如果整个坏掉了&#xff0c;重新搭建部署起来需…

lqb——修改数组

思路 **常规思路用哈希表的思想,设置bool数组标识是否被占用过,但是发生矛盾时将会造成查找需要遍历整个数组,比如,1,2,3……100000已连续占用,此时再插入1,将会一直遍历这100000个数,极端情况下,插入100000个1,将是n平方的复杂度。 如何快速查找到插入位置,这就引…