文章目录
- 一、先搞懂两个核心内存区域
-
- [1.托管内存(CLR 说了算)](#1.托管内存(CLR 说了算))
- 2.非托管内存(操作系统说了算)
- 二、什么是【托管对象】?
- [三、什么是【非托管对象 / 非托管资源】?](#三、什么是【非托管对象 / 非托管资源】?)
- [四、为什么 GC 不能回收非托管资源?](#四、为什么 GC 不能回收非托管资源?)
- 五、一张表彻底分清(超好记)
- [六、C# 正确处理非托管资源的三种方式](# 正确处理非托管资源的三种方式)
- 七、高频误区纠正(新手最容易错)
- 八、终极一句话总结
C# 托管对象、非托管对象
先建立底层认知:
C# 跑在 .NET CLR(公共语言运行时) 里,
CLR 就像「小区物业」,物业管得到的 = 托管 ,物业管不到的 = 非托管。
一、先搞懂两个核心内存区域
1.托管内存(CLR 说了算)
由 CLR 统一申请、分配、管理、回收。
包含:
- 栈内存(局部变量、值类型)
- 托管堆(引用类型对象)
2.非托管内存(操作系统说了算)
CLR 无权管理,是程序直接向Windows 系统内核 申请的资源。
包含:文件句柄、数据库连接、网络端口、图像画笔、原生 C++ 内存等。
二、什么是【托管对象】?
1.通俗定义
完全由 CLR + GC 垃圾回收机制 全权管理的对象。
你只管 new 创建,不用管销毁、不用管释放内存,程序会自动帮你清理垃圾。
2.核心特点
- 内存开在「托管堆 / 托管栈」,属于 .NET 自家地盘
- 生命周期自动控制,你不用手动写代码释放
- 程序闲置、内存不够时,GC 自动扫描 + 回收没人用的对象
- 不会内存泄漏(极端情况除外)
3.哪些是托管对象?(全覆盖)
- 所有自定义 class 类
- 常见引用类型:string、List、Dictionary、数组
- 普通值类型:int、double、bool、struct、DateTime
- .NET 基础工具类:集合、实体类、普通业务对象
4.举个最简单例子
csharp
// 全部都是托管对象
int num = 10;
string name = "张三";
List<string> list = new List<string>();
Student stu = new Student();
👉 这些变量用完没人引用了,GC 悄悄自动删掉,释放内存,你完全不用操心。
三、什么是【非托管对象 / 非托管资源】?
1.通俗定义
CLR 和 GC 完全看不懂、管不着、回收不了的系统资源。
这些资源是程序直接跟操作系统借来的,GC 只认识 .NET 自己的内存,不认识文件、端口、数据库、图片句柄。
关键区别:
托管 = 内存资源
非托管 = 系统硬件 / 内核资源
2.核心特点
- 不归 CLR 管,GC 不会自动回收
- 用完必须手动释放,不然一直占用
- 长期不释放 → 句柄泄漏、内存泄漏、程序越跑越卡、甚至崩溃
- 统一规范:实现 IDisposable 接口来释放
3.常见非托管资源(必记)
- 文件类:FileStream、StreamReader、StreamWriter(文件句柄)
- 数据库:SqlConnection、MySqlConnection、数据库事务
- 网络类:Socket、TcpClient、UdpClient、网络连接端口
- 绘图 GDI:Bitmap、Graphics、Brush、Font(画图资源)
- 系统底层:窗口句柄、注册表、进程、互斥体
- 混合开发:调用 C++/C 语言 DLL 申请的内存、指针、裸内存
4.举个反例(危险写法)
csharp
// FileStream 包含非托管文件句柄
FileStream fs = new FileStream("a.txt", FileMode.Open);
// 只打开,不关闭、不释放
// GC 回收不了文件句柄!
四、为什么 GC 不能回收非托管资源?
- GC 的工作:只扫描、清理 .NET 托管堆里的内存对象
- 文件、端口、数据库连接,是操作系统内核里的资源
- CLR 没有权限、也没有逻辑去感知:
「这个文件有没有用完?这个端口要不要关掉?」 - 所以:系统借出去的资源,必须你自己主动还给系统
五、一张表彻底分清(超好记)
| 对比维度 | 托管对象 | 非托管资源 / 对象 |
|---|---|---|
| 管理者 | CLR + GC 自动管理 | Windows 操作系统 |
| 内存位置 | 托管栈、托管堆 | 系统内核、非托管内存 |
| 释放方式 | 自动垃圾回收,无需手动 | 必须手动 Dispose / using |
| 会不会泄漏 | 基本不会 | 不手动释放,必然泄漏 |
| 识别标志 | 普通 class、值类型 | 实现 IDisposable 接口 |
| 本质 | 纯内存数据 | 文件 / 端口 / 连接 / 硬件句柄 |
六、C# 正确处理非托管资源的三种方式
只要类能点出 .Dispose(),就一定要释放。
方式 1:using 语句(最推荐、最简单)
using 块执行结束,自动强制释放非托管资源,不用手写代码。
csharp
// 出了大括号,自动调用 Dispose 释放文件句柄
using (FileStream fs = new FileStream("a.txt", FileMode.Open))
{
// 读写文件操作
}
方式 2:手动调用 Dispose () / Close ()
适合资源需要跨方法、长期使用的场景
csharp
SqlConnection conn = new SqlConnection("连接字符串");
conn.Open();
// 业务逻辑...
// 手动归还系统资源
conn.Dispose();
补充:大部分 Close() 底层内部就是调用 Dispose()。
方式 3:自定义类实现 IDisposable(进阶)
如果你自己写的类里,封装了非托管资源,就要按规范写释放逻辑:
- 分开释放:先释放非托管,再释放托管
- 加标记防止重复释放
- 用终结器 ~类名() 做兜底保护
七、高频误区纠正(新手最容易错)
误区 1:值类型是 non 托管
❌ 错int、struct、bool 全是托管环境下的值类型,属于托管资源,GC 统一管理。
误区 2:只要是 new 出来的就是非托管
❌ 错new List() 是托管;new FileStream() 才是非托管。
误区 3:不用 using,程序结束就没事
❌ 错程序运行期间会持续泄漏,导致:打开文件越来越慢、数据库连接爆满、端口占用、软件卡顿。
八、终极一句话总结
- 托管对象:CLR 物业全包,自动打扫垃圾,你只管随便用;
- 非托管资源:向系统借的东西,物业管不了,用完必须主动归还,不然越用越卡、资源卡死;
- 判断口诀:能写 using 的,全是带非托管资源的,必须释放。