当前位置: 首页 > news >正文

【一文了解】闭包

目录

闭包

1.C#和Unity中闭包的本质

1.1.闭包的定义

1.2.基本示例

1.3.闭包的底层原理

2.多线程+循环中闭包的典型问题

3.处理循环变量的核心技巧:“变量快照”

4.Unity中闭包的其他注意事项

4.1.闭包与协程的结合

4.2.闭包与内存泄漏

5.闭包使用技巧


       本篇文章分享一下在C#和Unity中的闭包(Closure)。在C#和Unity中,闭包(Closure)是指匿名函数(或lambda表达式)捕获并访问其外部作用域变量的现象。这种特性在简化代码的同时,也可能引发意外行为,尤其是在多线程和循环结合的场景中。

闭包

1.C#和Unity中闭包的本质

1.1.闭包的定义

       闭包是匿名函数(或lambda表达式)捕获并访问其外部作用域变量的现象。即使外部变量所在的作用域已销毁,匿名函数仍能访问和修改这些变量。

1.2.基本示例

private void Start()
{int count = 0;//外部变量//匿名函数捕获 count,形成闭包Action fun = () => {count++;Debug.Log($"count: {count}");};fun();//输出: count: 1fun();//输出: count: 2
}

       匿名函数()=>{}捕获了外部变量count,即使Start方法执行结束,fun仍能修改count。

1.3.闭包的底层原理

       C#编译器会为闭包创建一个匿名类,将捕获的变量作为该类的字段,匿名函数作为类的方法。例如,上面的代码会被编译为类似:

//编译器生成的匿名类
private sealed class AnonymousClass
{public int count; // 捕获的外部变量public void Fun(){count++;Debug.Log($"count: {count}");}
}// Start 方法实际执行逻辑
private void Start()
{AnonymousClass obj = new AnonymousClass();obj.count = 0;Action fun = obj.Fun;fun();fun();
}

2.多线程+循环中闭包的典型问题

       在循环中创建多线程并使用闭包捕获循环变量时,容易出现变量值不符合预期的问题。这是因为闭包捕获的是变量本身,而非变量在循环某一轮的值。如Unity中多线程读取文件(错误示例):

private void Start()
{string[] filePaths = { "file1.txt", "file2.txt", "file3.txt" };foreach (var path in filePaths){//错误:直接捕获循环变量 pathnew Thread(() => {Debug.Log($"读取文件: {path}");//输出多个重复路径(如 3 次 "file3.txt")}).Start();}
}

       问题原因:foreach循环的迭代变量path在编译时被视为循环体外定义的单一变量(所有迭代共享同一个变量)。线程启动后,循环可能已执行多轮,path的值已改变,导致多个线程读取到相同的最终值。

3.处理循环变量的核心技巧:“变量快照”

       解决上述问题的关键是在每轮循环中创建临时变量,固化当前迭代的变量值,让闭包捕获临时变量而非循环变量。即“变量快照”,在特定时刻为变量创建一个“副本”,固化其当前值,避免后续代码对原变量的修改影响到依赖该变量的逻辑。简而言之,“变量快照”就是“给变量拍个照存起来,用的时候看照片,不管原变量后来变成啥样”。如Unity中多线程读取文件(正确示例:多线程+循环+闭包)

private void Start()
{string[] filePaths = { "file1.txt", "file2.txt", "file3.txt" };foreach (var path in filePaths){//变量快照:每轮循环创建临时变量,保存当前 path 的值string currentPath = path; new Thread(() => {Debug.Log($"读取文件: {currentPath}");//正确输出每个文件路径}).Start();}
}

       原理:currentPath是循环每轮创建的新变量,值为当前迭代的path。闭包捕获的是currentPath,其值在本轮循环中固定,因此每个线程会使用正确的路径。

4.Unity中闭包的其他注意事项

4.1.闭包与协程的结合

       在协程中使用闭包时,需注意捕获的变量可能被延迟修改:

using UnityEngine;
using System;
using System.Collections;public class ClosureTest1 : MonoBehaviour
{private IEnumerator Start(){int frame = 0;//协程中启动另一个协程,闭包捕获 frameStartCoroutine(MyCoroutine(() =>{Debug.Log($"当前帧: {frame}");//输出的是 frame 的最终值(而非启动时的值)}));//延迟修改 frameyield return new WaitForSeconds(1);frame = 100;}private IEnumerator MyCoroutine(Action callback){yield return new WaitForSeconds(2);//等待 2 秒,此时 frame 已被修改为 100callback();//输出: 当前帧: 100}
}

       解决:同样使用变量快照:

using UnityEngine;
using System;
using System.Collections;public class ClosureTest1 : MonoBehaviour
{private IEnumerator Start(){int frame = 0;int currentFrame = frame;//固化当前值StartCoroutine(MyCoroutine(() =>{Debug.Log($"当前帧: {currentFrame}");}));yield return new WaitForSeconds(1);frame = 100;}private IEnumerator MyCoroutine(Action callback){yield return new WaitForSeconds(2);callback();//输出: 当前帧: 0}
}

4.2.闭包与内存泄漏

       闭包会延长捕获变量的生命周期,若变量是Unity组件(如GameObject、MonoBehaviour),可能导致内存泄漏:

public class ClosureTest2 : MonoBehaviour
{private void Start(){GameObject obj = new GameObject("Temp");//闭包捕获 obj,即使 obj 被销毁,闭包仍持有引用Action action = () => Debug.Log(obj.name);Destroy(obj);//销毁对象,但闭包仍引用 obj,导致其无法被 GC 回收}
}

       解决:避免在长生命周期的闭包中捕获Unity组件。必要时手动解除引用(如设为null)。

5.闭包使用技巧

(1)多线程+循环:每轮循环定义临时变量(如上述例子中的currentPath = path),让闭包捕获临时变量,避免共享循环变量导致的值混乱。

(2)变量快照:用临时变量固化捕获的值,确保闭包使用的是预期值而非延迟修改后的值。

(3)内存管理:避免闭包长期持有Unity组件引用,必要时手动释放,防止内存泄漏。

(4)调试技巧:若闭包行为异常,可将匿名函数改为显式方法,通过参数传递变量值,降低调试难度。

       合理利用闭包能简化代码,但需注意其对变量生命周期的影响,尤其是在多线程和循环场景中。

       好了,本次的分享到这里就结束啦,希望对你有所帮助~

http://www.dtcms.com/a/390502.html

相关文章:

  • 深入解析Mysql数据库并发:从读写机制到多版本控制
  • Python自学20 - Python操作PDF文件
  • Windows 本地 UV 环境部署 Index-TTS2 实战:基于 EPGF 架构的完整指南(支持 DeepSpeed + FP16)
  • 【cpp Trip第4站】函数参数传递
  • 【Spotfire】实现错行效果
  • 【Day 61】Linux-haproxy负载均衡
  • 搭建线上线下融合的商城小程序,关键步骤有哪些?
  • 软件测试教程资源合集
  • Proteus(8.17)SP2 仿真下载安装过程(附详细安装过程图)
  • 微软获评 2025 Gartner 云原生应用平台魔力象限 领导者
  • Java 生态监控体系实战:Prometheus+Grafana+SkyWalking 整合全指南(一)
  • 【学习】响应系统
  • Linux网络:socket网络套接字
  • 知识图谱对人工智能中自然语言处理的深层语义分析的影响与启示
  • 从车间到云端:Kepware如何加速IIoT落地
  • MyISAM 与 InnoDB 深度对比:如何正确选择 MySQL 存储引擎
  • 串口无线数传模块实现化工园区与3公里外水泵PLC无线通讯实现设备数据传输
  • rook-ceph自定义添加osd流程
  • 【需求导向】解读660页智慧教育大数据信息化顶层设计及智慧应用建设方案
  • InnoDB 引擎深潜指南---从逻辑结构到 MVCC 原理,带源码级案例与性能要点
  • Android Compose 开发 界面间的跳转(Router)
  • unity(C#/cs)请求 python django后端服务器预制体渲染 scroll list 视频列表
  • 《Linux 指令实战进阶:从终端新手到 shell 驾驭者的技术跃迁(第三篇)》
  • 临床AI产品化全流程研究:环境聆听、在环校验与可追溯系统的多技术融合实践(下)
  • Croe 11.0 学习笔记:1.5 草绘
  • Hadoop 1.x 与 2.x 版本对比:架构演进与核心差异解析
  • 【5/20】Express.js 基础:构建 RESTful API,实现用户数据端点
  • SmartX 榫卯企业云平台+ StarRocks 大数据联合解决方案
  • CodeX 新王已来:从技术原理到工程实践,AI 如何重构编程全流程
  • 智慧赋能:King‘s Biobank 重构生物样本管理新范式