ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# 析构函数全详解:原理+实战+避坑

C# 析构函数全详解:原理+实战+避坑 很多学过 C 的同学初学 C# 都会踩一个大坑试图用 C 的析构思维写 C# 代码。在 C 中析构函数是确定性执行的对象销毁立刻触发但在 C# 中析构函数完全是另一套逻辑乱用会导致内存泄漏、程序卡顿、资源残留等问题。今天这篇博文一次性讲透C# 析构函数是什么、什么时候执行、怎么写、怎么用、以及90%人都会犯的错误附带全套可运行代码。一、什么是 C# 析构函数1. 核心定义析构函数终结器是类的特殊方法由CLR 垃圾回收器GC自动调用作用是兜底释放非托管资源。简单记住两句话托管资源 GC 自动管不用你操心非托管资源 GC 管不了靠析构函数兜底2. 标准语法规则语法特征极强一眼就能识别以~波浪号开头 类名无返回值、无参数、无访问修饰符不能加 public/private一个类只能有一个析构函数不支持重载3. 最简入门代码示例using System; namespace DestructorDemo { public class Student { // 构造函数创建对象时执行 public Student() { Console.WriteLine(对象创建构造函数执行); } // 析构函数GC回收对象前自动执行 ~Student() { Console.WriteLine(对象销毁析构函数执行); } } class Program { static void Main(string[] args) { // 创建对象 Student stu new Student(); // 主动置空让GC可以回收 stu null; // 手动触发垃圾回收仅测试用项目中禁止写 GC.Collect(); GC.WaitForPendingFinalizers(); } } }运行结果对象创建构造函数执行对象销毁析构函数执行 注意日常开发绝对不要手动写 GC.Collect()此处仅为测试让大家直观看到析构函数效果。二、析构函数 VS 构造函数图文对比逻辑为了方便大家理解我用最直白的生命周期逻辑展示生命周期流程图解【代码执行 new】→ 构造函数初始化资源 ↓ 【对象使用中】→ 执行业务逻辑 ↓ 【对象无引用、闲置】→ GC扫描标记 ↓ 【垃圾回收】→ 析构函数清理资源 ↓ 【对象彻底销毁】核心区别对照表对比维度构造函数析构函数执行时机new 对象时确定性执行GC回收时不确定时机调用者开发者代码主动调用CLR 垃圾回收器自动调用作用初始化字段、开辟资源兜底释放非托管资源数量可多个、可重载只能1个、不可重载手动调用支持完全不支持三、重中之重托管资源 非托管资源能不能用好析构函数核心就在于分清这两种资源1. 托管资源GC 自动回收由 .NET 框架管理的资源GC 会自动清理不需要析构函数例string、List、数组、自定义类对象 等2. 非托管资源GC 管不了必须兜底操作系统原生资源.NET 无法自动识别回收必须手动释放/析构兜底例文件流、数据库连接、网络套接字、IntPtr 非托管内存、窗口句柄核心结论 没有非托管资源的类坚决不写析构函数只会增加 GC 性能开销四、企业级标准写法Dispose 释放模式纯析构函数有致命问题执行时机太晚、不确定。 所以 .NET 官方规范IDisposable 析构函数 双保险模式用户主动 using/Dispose立刻释放所有资源高效、及时用户忘记释放析构函数兜底防止内存泄漏完整可运行实战代码using System; namespace DestructorDemo { // 实现释放接口 public class FileResource : IDisposable { private IntPtr _unmanagedHandle; // 模拟非托管资源句柄 private bool _isDisposed false; // 构造函数占用非托管资源 public FileResource() { Console.WriteLine(获取非托管资源句柄); _unmanagedHandle (IntPtr)10086; // 模拟资源赋值 } // 公共释放方法给开发者手动调用 public void Dispose() { Dispose(true); // 告诉GC资源已释放无需执行析构函数 GC.SuppressFinalize(this); Console.WriteLine(手动释放资源完成跳过析构执行); } // 核心释放逻辑 protected virtual void Dispose(bool isManualDispose) { if (_isDisposed) return; // 主动释放时可清理托管资源 if (isManualDispose) { Console.WriteLine(清理托管资源); } // 无论手动/GC回收必须清理非托管资源 if (_unmanagedHandle ! IntPtr.Zero) { Console.WriteLine(释放非托管资源句柄); _unmanagedHandle IntPtr.Zero; } _isDisposed true; } // 析构函数GC兜底执行 ~FileResource() { Dispose(false); Console.WriteLine(析构函数兜底释放完成); } } class Program { static void Main(string[] args) { // using 语法糖出作用域自动调用Dispose using (var file new FileResource()) { Console.WriteLine(正在使用资源...); } Console.WriteLine(程序执行结束); } } }运行结果获取非托管资源句柄 正在使用资源... 清理托管资源 释放非托管资源句柄 手动释放资源完成跳过析构执行 程序执行结束可以看到手动 Dispose 成功后析构函数不会执行性能最优五、析构函数 4 大高频误区避坑必看误区1C# 析构 C 析构错误C 析构确定性执行C# 析构完全随机程序退出都可能不执行正确需要及时释放的资源一律用using Dispose误区2析构函数中操作托管对象错误GC 回收顺序不可控其他托管对象可能已经被回收直接报空引用异常正确析构中只处理非托管资源误区3所有类都写析构函数错误无非托管资源时写析构会让对象进入终结队列降低GC回收效率正确无原生资源不写析构函数误区4手动调用析构函数错误C# 禁止手动调用 ~类名() 和 Finalize()正确资源释放统一用 Dispose六、总结博文核心干货析构函数是GC 自动兜底的非托管资源清理方法执行时机不确定语法~类名()无参无返回值唯一不可重载托管资源不用管非托管资源必须靠Dispose析构双保险业务开发优先使用using 语法糖杜绝手动 GC 操作无原生非托管资源坚决不写析构函数避免性能损耗码字不易欢迎点赞收藏后续持续更新 C# 底层原理、.NET 性能优化、实战避坑干货
返回列表