C# 托管对象、非托管对象 讲解

文章目录

C# 托管对象、非托管对象

先建立底层认知:

C# 跑在 .NET CLR(公共语言运行时) 里,

CLR 就像「小区物业」,物业管得到的 = 托管物业管不到的 = 非托管

一、先搞懂两个核心内存区域

1.托管内存(CLR 说了算)

由 CLR 统一申请、分配、管理、回收。

包含:

  • 栈内存(局部变量、值类型)
  • 托管堆(引用类型对象)

2.非托管内存(操作系统说了算)

CLR 无权管理,是程序直接向Windows 系统内核 申请的资源。

包含:文件句柄、数据库连接、网络端口、图像画笔、原生 C++ 内存等。


二、什么是【托管对象】?

1.通俗定义

完全由 CLR + GC 垃圾回收机制 全权管理的对象。

你只管 new 创建,不用管销毁、不用管释放内存,程序会自动帮你清理垃圾。

2.核心特点

  1. 内存开在「托管堆 / 托管栈」,属于 .NET 自家地盘
  2. 生命周期自动控制,你不用手动写代码释放
  3. 程序闲置、内存不够时,GC 自动扫描 + 回收没人用的对象
  4. 不会内存泄漏(极端情况除外)

3.哪些是托管对象?(全覆盖)

  1. 所有自定义 class 类
  2. 常见引用类型:string、List、Dictionary、数组
  3. 普通值类型:int、double、bool、struct、DateTime
  4. .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.核心特点

  1. 不归 CLR 管,GC 不会自动回收
  2. 用完必须手动释放,不然一直占用
  3. 长期不释放 → 句柄泄漏、内存泄漏、程序越跑越卡、甚至崩溃
  4. 统一规范:实现 IDisposable 接口来释放

3.常见非托管资源(必记)

  1. 文件类:FileStream、StreamReader、StreamWriter(文件句柄)
  2. 数据库:SqlConnection、MySqlConnection、数据库事务
  3. 网络类:Socket、TcpClient、UdpClient、网络连接端口
  4. 绘图 GDI:Bitmap、Graphics、Brush、Font(画图资源)
  5. 系统底层:窗口句柄、注册表、进程、互斥体
  6. 混合开发:调用 C++/C 语言 DLL 申请的内存、指针、裸内存

4.举个反例(危险写法)

csharp 复制代码
// FileStream 包含非托管文件句柄
FileStream fs = new FileStream("a.txt", FileMode.Open);

// 只打开,不关闭、不释放
// GC 回收不了文件句柄!

四、为什么 GC 不能回收非托管资源?

  1. GC 的工作:只扫描、清理 .NET 托管堆里的内存对象
  2. 文件、端口、数据库连接,是操作系统内核里的资源
  3. CLR 没有权限、也没有逻辑去感知:
    「这个文件有没有用完?这个端口要不要关掉?」
  4. 所以:系统借出去的资源,必须你自己主动还给系统

五、一张表彻底分清(超好记)

对比维度 托管对象 非托管资源 / 对象
管理者 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. 分开释放:先释放非托管,再释放托管
  2. 加标记防止重复释放
  3. 用终结器 ~类名() 做兜底保护

七、高频误区纠正(新手最容易错)

误区 1:值类型是 non 托管

❌ 错int、struct、bool 全是托管环境下的值类型,属于托管资源,GC 统一管理。

误区 2:只要是 new 出来的就是非托管

❌ 错new List() 是托管;new FileStream() 才是非托管。

误区 3:不用 using,程序结束就没事

❌ 错程序运行期间会持续泄漏,导致:打开文件越来越慢、数据库连接爆满、端口占用、软件卡顿。


八、终极一句话总结

  1. 托管对象:CLR 物业全包,自动打扫垃圾,你只管随便用;
  2. 非托管资源:向系统借的东西,物业管不了,用完必须主动归还,不然越用越卡、资源卡死;
  3. 判断口诀:能写 using 的,全是带非托管资源的,必须释放
相关推荐
HappyAcmen2 小时前
10.常见报错排查与基础调试
开发语言·python
码农的神经元2 小时前
配电网智能决策平台:从风险感知到自愈控制的 Python 实现
开发语言·python
xlq223222 小时前
46.线程池
linux·开发语言
LF男男2 小时前
Action- C# 内置的委托类型
java·开发语言·c#
记录无知岁月2 小时前
【C/C++】头文件包含问题分析
c语言·开发语言·c++
神仙别闹2 小时前
基于Python实现(控制台)个人信息系统
开发语言·python
HoneyMoose2 小时前
Discourse 更加依赖 tag 的扁平化管理
开发语言
Hello eveybody2 小时前
介绍最大公因数和最小公约数(Python)
开发语言·python
谭欣辰2 小时前
C++ 堆 的基础与 二叉堆详解
开发语言·c++