新来的总监,把C#闭包讲得那叫一个透彻

闭包作为前端面试的必考题目,常让1-3年工作经验的Javascripter感到困惑,其实C#语言也有闭包。

今天我们深入聊一聊[闭包], 查缺补漏!

  1. C#闭包的实现 · 庖丁解牛
  2. 跨语言 · 追本溯源
    • 一等函数
    • 自由变量
    • 词法作用域
  3. 闭包与线程结合

1. C#闭包: 庖丁解牛

一个闭包就是一个“捕获”或“携带”了其生成的环境中、所引用的自由变量的函数。
这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

 static void Closure()
 {
      var x = 1;
      Action action= () =>
         {
             var y = 1;
             var result = x + y;
             Console.WriteLine(result);
             x++;
         };
      action();
      action();
}
 //  调用函数输出
  2
  3

我们首先定义了一个委托action,它引用了“x”变量(x变量既不是入参,也不是委托函数内的局部变量), 这个变量将被action"捕获”,被自动添加到action 的运行环境中了。

当我们执行action时,原始的“x”已经脱离了它被引用时的作用域环境,但是两次执行能输出2,3 说明它脱离原引用环境仍然能用。


当你在代码调试器(debugger)里观察“action”时,会发现很有趣的事情,可以看到一个Target属性,里面封装了捕获的x变量:

实际上,委托,匿名函数和lambda都是继承自Delegate类,只有两个属性

  • Method:方法执行体
  • Target:当前委托执行的对象,包含了关键的自由变量. ( 也就是将捕获自由变量的过程封装成了对象 )。
    Action函数的执行时空和 Action捕获的自由变量所在的作用域 不是一个时空。

至此可以窥见“闭包”的实质:

  • 在实现了Delagate的封装类上执行方法体, 我们每次执行委托,实际是是执行封装类上的实例方法(Method属性),提前捕获的自由变量被存储在封装类的Target属性。

  • 从我们打debugger端点的时机看,Action执行前已经捕获到了自由变量, 这个观点也很重要


闭包是跨越语言的设计, 至少我知道 Javascript C# 都有闭包。

2. 追本溯源

闭包是词法闭包的简称,维基百科上是这样定义的:
在计算机科学中,闭包是在词法环境中绑定自由变量的头等函数”。

头等函数

头等函数( First Class)意味着语言将其视为第一类数据类型的函数, 意味着你可以将函数分配给一个变量(或作为参数传递),然后像正常函数一样调用。

很明显,在C#中我们常使用的匿名函数、lambda表达式都是头等函数。

Func<string,string> myFunc = delegate(string var1)
                                {
                                    return "some value";   
                                };
Func<string,string> myFunc = var1 => "some value";  

string myVar = myFunc("something");

自由变量

自由变量只是一个在函数中被引用的变量,它不是函数的参数也不是函数的局部变量。

var myVar = "this is good";

Func<string,string> myFunc = delegate(string var1)
                                {
                                    return var1 + myVar;   
                                };

词法作用域引用的自由变量,注意,是引用自由变量,并不是使用当时自由变量的值

☺️通俗点, 就是告知这个变量环境,我这个匿名函数等会执行时要用到这个变量;如果我没被销毁,你不能销毁我引用的自由变量。

C#自由变量会在原词法作用域被捕获进 闭包。

我们再回过头来看一个结合了线程的闭包面试题。

3. 闭包结合线程

 static void Closure1()
{
    for (int i = 0; i < 5; i++)
    {                 
         Task.Run(()=> Console.WriteLine(i));
    }
 }
//  输出数字是不固定的:
3
3
3
5
5
 

也不一定是

5
5
5
5
5

并不是预期的 0.1.2.3.4.

for循环,快速开启了5个Task任务,每个任务引用的i就是自由变量; i相对于这5个任务就成了 并发访问的全局变量。

5个任务捕获的值是不固定的。

加上临时变量就能输出乱序的0,1,2,3,4。

这是因为在for循环内,每次都有一个局部变量j(拷贝了i的值),这样每个任务执行环境均维护了一个变量j, 这个j不是全局变量;

任务乱序执行时依旧能获取本任务绑定的自由变量值j。


或者可以换成foreach,foreach相当于内部给你整了一个局部变量。

        var ss = new int[] { 0, 1, 2, 3, 4 };
        foreach (var item in ss)
        {
            Task.Run(() => Console.WriteLine(item));
        }

总结

本文屏蔽语言差异,理清了[闭包]的概念核心: 头等函数、自由变量,不仅能帮助我们应对多语种有关闭包的面试题, 也帮助我们了解[闭包]在通用语言中的设计初衷。

另外我们通过C# 调试器巩固了Delegate 抽象类,这是lambda表达式,委托,匿名函数的底层抽象数据结构类,包含两个重要属性 Method Target,分别表征了方法执行体、当前委托作用的类对象,

可想而知,其他语言也是通过这个机制捕获闭包当中的自由变量。

20231206 补充一个golang gin框架闭包的应用

gin 框架中中间件的默认形态是:

package middleware
func AuthenticationMiddleware(c *gin.Context) {
   ......
}

 //  Use方法的参数签名是这样:  type HandlerFunc func(*Context), 不支持入参
router.Use(middleware.AuthenticationMiddleware)   

实际实践上我们又需要给中间件传参, 闭包提供了这一能力。

func Authentication2Middleware(log *zap.Logger) gin.HandlerFunc  {
     return func(c *gin.Context) { 
         ...    这里面可以利用log 参数。
     }
}

var logger  *zap.Logger
api.Use(middleware.Authentication2Middleware(logger))

原文地址:https://www.cnblogs.com/JulianHuang/p/14618378.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


引言 本文从Linux小白的视角, 在CentOS 7.x服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用。 在开始之前,我们还是重温一下部署原理,正如你所常见的.Net Core 部署图: 在Linux上部署.Net Core App最好的方式是在Linux机器
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式。 相信很多开发者都看到如下异步编程实践原则: 遵守以上冷冰冰的②③条的原则,可保证异步程序按照预期状态正常运作;我们在各大编程论坛常看到违背
一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件。 每个组件是pipeline 中的一环。 自行决定是否将请求传递给下一个组件 在处理管道的下个组件执行之前和之后执行业务逻辑 二. 特性和行为 ASP.NET Core处
背景 在.Net和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文,可以掌握 通过CancellationToken取消任务(包括non-cancellable任务)。 Task&#160;表示无返回值的异步操作, 泛型版本Task&lt;TResult&gt;表示有返
HTTP基本认证 在HTTP中,HTTP基本认证(Basic Authentication)是一种允许网页浏览器或其他客户端程序以(用户名:口令) 请求资源的身份验证方式,不要求cookie,session identifier、login page等标记或载体。 - 所有浏览器据支持HTTP基本认
1.Linq 执行多列排序 OrderBy的意义是按照指定顺序排序,连续两次OrderBy,后面一个有可能会打乱前面一个的排序顺序,可能与预期不符。 要实现sql中的order by word,name类似效果; LINQ 有ThenBy可以紧接使用, ThenBy记住原本排序的值,然后再排其他值,
ASP.NET Core 核心特性:开源、跨平台、高性能是其决战JAVA的必胜法宝,最引人关注的跨平台特性 到底是怎么实现? &#xA; 本文分Unix、Windows剖析跨平台内幕,读完让你大呼过瘾。
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现。 IAsyncResult BeginGetResponse(AsyncCallback callback, object state
引言 最近在公司开发了一个项目,项目部署架构图如下: 思路 如图中文本所述,公司大数据集群不允许直接访问外网,需要一个网关服务器代理请求,本处服务器A就是边缘代理服务器的作用。 通常技术人员最快捷的思路是在服务器A上部署IISʺpplication Request Routing Module组件
作为一枚后端程序狗,项目实践常遇到定时任务的工作,最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主机上部署定时任务程序/脚本。 但是很多时候,若使用的是共享主机或者受控主机,这些主机不允许你私自安装exe程序、Windows服务程序
引言 熟悉TPL Dataflow博文的朋友可能记得这是个单体程序,使用TPL Dataflow 处理工作流任务, 在使用Docker部署的过程中, 有一个问题一直无法回避: 在单体程序部署的瞬间(服务不可用)会有少量流量无法处理;更糟糕的情况下,迭代部署的这个版本有问题,上线后无法运作, 更多的流
合格的web后端程序员,除搬砖技能,还必须会给各种web服务器配置Https,本文结合ASP.NET Core部署模型聊一聊启用Https的方式。 温故知新 目前常见的Http请求明文传输,请求可能被篡改,访问的站点可能被伪造。 HTTPS是HTTP加上TLS/SSL协议构建的可进行加密传输、身份认
长话短说 前文《解剖HttpClientFactory,自由扩展HttpMessageHandler》主要讲如何为HttpClientFactory自定义HttpMessageHandler组件, 现在来完成课后的小作业: 将重点日志字段显示到Nlog的LayoutRenderer上。 本文实现一个
引言问题 作为资深老鸟,有事没事,出去面试;找准差距、定位价值。 面试必谈哈希, Q1:什么是哈希? Q2:哈希为什么快? Q3:你是怎么理解哈希算法利用空间换取时间的? Q4:你是怎么解决哈希冲突的? Q5:你有实际用写过哈希算法吗? 知识储备 哈希(也叫散列)是一种查找算法(可用于插入),哈希算
前言 如题,有感于博客园最近多次翻车,感觉像胡子眉毛一把抓, 定位不了生产环境的问题。 抛开流程问题,思考在生产环境中如何做故障排除,&#160;发现博客园里面这方面的文章比较少。 .Net 本身是提供了sos.dll工具帮助我们在生产中故障排除,通过提供有关内部公共语言运行时(CLR)环境的信息,
.NET程序是基于.NET Framework、.NET Core、Mono、【.NET实现】开发和运行的 ,定义以上【.NET实现】的标准规范称为.NET Standard .NET Standard .NET标准是一组API集合,由上层三种【.NET实现】的Basic Class Library
长话短说 上个月公司上线了一个物联网数据科学项目,我主要负责前端接受物联网事件,并提供 参数下载。 webapp 部署在Azure云上,参数使用Azure SQL Server存储。 最近从灰度测试转向全量部署之后,日志时常收到: SQL Session超限报错。 排查 我在Azure上使用的是 S
临近年关,搜狗,360浏览器出现页面无法成功跳转,同域Cookie丢失? 也许是服务端 SameSite惹的祸。&#xA;本文揭示由于Chrome低版本内核不识别 SameSite= None, 引发的单点登录故障。
本文聊一聊TraceID的作用和一般组成,衍生出ASP. NETCore 单体和分布式程序中 TraceId 的使用方式
通过给 HttpClint请求的日志增加 TraceId,解锁自定义扩展 HttpClientFacroty 的姿势