C#中闭包概念讲解
作者:黑洞视界 发布时间:2022-08-16 05:16:28
理解C#中的闭包
1、 闭包的含义
首先闭包并不是针对某一特定语言的概念,而是一个通用的概念。除了在各个支持函数式编程的语言中,我们会接触到它。一些不支持函数式编程的语言中也能支持闭包(如java8之前的匿名内部类)。
在看过的对于闭包的定义中,个人觉得比较清晰的是在《JavaScript高级程序设计》这本书中看到的。具体定义如下:
闭包是指有权访问另一个函数作用域中的变量的函数。
注意,闭包这个词本身指的是一种函数。而创建这种特殊函数的一种常见方式是在一个函数中创建另一个函数。
2、 在C# 中使用闭包(例子选取自《C#函数式程序设计》)
下面我们通过一个简单的例子来理解C#闭包
class 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));
}
上述这两点变化在一定程度上能够带来性能的提升,目前的理解是,用结构体代替类,结构体实例能够在方法跑完后就立即释放,不需要等待垃圾回收,所以在官方的推荐中,如果委托的使用不是必要的,更推荐使用本地函数而非匿名函数。
来源:https://www.cnblogs.com/blurhkh/p/9535289.html


猜你喜欢
- 本文实例讲述了C#保存listbox中数据到文本文件的方法。分享给大家供大家参考。具体实现方法如下:private void SaveLst
- 一、问题分析入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig的配置类,再回想前面咱们学习Spring的时候
- Android 使用log4j前言: 如果要直接在android工程中使用log4j,是有点问题的,会报如下的错: 1
- 一、在相应的板块中开启DataBinding dataBinding {
- springboot logback动态获取application的配置项在多环境的情况下,logback的日志路径需要进行针对性配置,也就
- 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些
- 有很多同学肯定想学习opencv相关的知识,但是有些情况下每建一次项目都要重新引入下各种文件是不是很苦恼,所以我也面临了这个问题,在网上看到
- 微信开放平台 : https://open.weixin.qq.com/一、准备工作 : 1. Android Studio环境下:在bui
- 本文实例为大家分享了Java实现登录和注册的具体代码,供大家参考,具体内容如下登录和注册案例的分析:我们在完成一个需求时,需要面向对象,我们
- 出于安全考虑,在后台与前台进行数据传输时,往往不会直接传输实体模型,而是使用Dto(Data transfer object 数据传输对象)
- 一、表创建一、表创建//创建一个空表DataTable dt = new DataTable();//创建一个名为"Table_N
- 1. 前言老板说,明天甲方要来看产品,你得造点数据,而且数据必须是“真”的,演示效果要好看一些,这样他才会买我们的产品,我好明年给你换个嫂子
- JavaFx初探一,UI控件的使用,具体内容如下方式一:使用纯代码直接new view控件,这样就不涉及到与fxml文件之间的交互了方式二:
- Swagger2配置(解决404报错)在spring boot项目中配置Swagger2,配置好了但是访问确实404,SwaggerConf
- Volatile关键字的作用主要有如下两个:1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。2. 顺序一致性
- 简介散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数
- 今天在接手别人的一个项目的时候遇到一个坑,坑死我了;是一个打包的问题,好不容易我把代码写完了准备打包测试了,结果java -jar xxx.
- 1 使用阿里的FastJson1.1 项目的pom.xml依赖<dependency> <groupId>com.a
- 分布式项目和传统项目的区别就是,分布式项目有多个服务,每一个服务仅仅只实现一套系统中一个或几个功能,所有的服务组合在一起才能实现系统的完整功
- 前言在上篇文章讲到了如何配置单数据源,但是在实际场景中,会有需要配置多个数据源的场景,比如说,我们在支付系统中,单笔操作(包含查询、插入、新