文章目录
一、IDisposable是什么?
在 C# 里,IDisposable接口的主要功能是对非托管资源进行释放操作。像文件句柄、数据库连接、网络连接这类资源,垃圾回收机制(GC)是没办法自动回收的。若不手动释放这些资源,就容易造成资源泄漏,使系统性能下降。借助实现IDisposable接口,你可以自定义资源清理的逻辑,保证资源能被及时释放
。
二、常见的业务应用场景
-
文件操作方面:在进行文件读写操作后,需要关闭文件流(FileStream、StreamReader)。
-
数据库交互场景:使用完数据库连接或者命令对象后,要释放它们(SqlConnection、DbCommand)。
-
网络通信过程:网络连接使用完毕,需将其关闭(HttpClient、TcpClient)。
-
图形资源处理:比如使用完Bitmap对象后要进行释放(Bitmap、Graphics)。
-
自定义非托管资源管理:当你封装了操作系统的一些句柄时。
1.使用IDisposable标准实现模式
完整实现模板
public class ResourceWrapper : IDisposable
{
private IntPtr unmanagedResource; // 非托管资源
private bool disposed = false; // 释放状态标记
// 构造函数:分配资源
public ResourceWrapper()
{
unmanagedResource = AllocateUnmanagedResource();
}
// 实现接口方法
public void Dispose()
{
Dispose(true); // 手动调用:释放托管+非托管资源
GC.SuppressFinalize(this); // 阻止GC调用析构函数
}
// 受保护的虚方法:控制资源释放逻辑
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// 释放托管资源(如:其他实现了IDisposable的对象)
managedResource?.Dispose();
}
// 释放非托管资源(如:关闭文件句柄、数据库连接等)
FreeUnmanagedResource(unmanagedResource);
unmanagedResource = IntPtr.Zero;
disposed = true;
}
}
// 析构函数:在GC回收对象时调用(仅释放非托管资源)
~ResourceWrapper()
{
Dispose(false);
}
// 使用资源的方法(检查对象是否已释放)
public void UseResource()
{
if (disposed)
throw new ObjectDisposedException(nameof(ResourceWrapper));
// 使用资源...
}
}
2.文件操作示例
代码如下(示例):
using System;
using System.IO;
class FileExample
{
public void ReadFile()
{ //使用 using 语句自动管理资源
using (FileStream fs = new FileStream("data.txt", FileMode.Open))
{
using (StreamReader reader = new StreamReader(fs))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
}
}
public void ReadFileManual()
{ //手动实现资源释放的传统方式
FileStream fs = null;
StreamReader reader = null;
try
{
fs = new FileStream("data.txt", FileMode.Open);
reader = new StreamReader(fs);
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
finally
{
if (reader != null)
reader.Dispose();
if (fs != null)
fs.Dispose();
}
}
}
class Program
{
static void Main()
{
try
{
// 创建示例文件
CreateSampleFile();
var example = new FileExample();
Console.WriteLine("=== 使用 using 语句读取文件 ===");
example.ReadFile();
Console.WriteLine("\n=== 使用手动方式读取文件 ===");
example.ReadFileManual();
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
static void CreateSampleFile()
{
string filePath = "data.txt";
string[] lines = {
"这是一个示例文本文件",
"用于演示 C# 中的文件读取操作",
"",
"第一行: 使用 using 语句自动管理资源",
"第二行: 使用手动方式释放资源",
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " 创建"
};
File.WriteAllLines(filePath, lines);
Console.WriteLine($"已创建示例文件: {Path.GetFullPath(filePath)}\n");
}
}
3.数据库操作示例
以下是一个使用 SQL Server 数据库查询用户信息的完整示例,包含实际的数据库操作和数据处理:
代码如下(示例):
using System;
using System.Data.SqlClient;
class DatabaseExample
{
public void QueryData()
{
// 替换为实际的数据库连接字符串
string connectionString = "Data Source=localhost;Initial Catalog=YourDatabase;Integrated Security=True";
try
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
// 打开数据库连接
connection.Open();
Console.WriteLine("数据库连接已打开");
// SQL 查询语句,获取用户ID和姓名
string query = "SELECT Id, Username, Email FROM Users WHERE IsActive = 1";
using (SqlCommand command = new SqlCommand(query, connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
// 检查是否有数据
if (reader.HasRows)
{
Console.WriteLine("用户列表:");
Console.WriteLine("ID\t用户名\t\t邮箱");
Console.WriteLine("-----------------------------------");
// 遍历结果集
while (reader.Read())
{
int id = reader.GetInt32(0);
string username = reader.GetString(1);
// 处理可为空的邮箱字段
string email = reader.IsDBNull(2) ? "未设置" : reader.GetString(2);
Console.WriteLine($"{id}\t{username}\t\t{email}");
}
}
else
{
Console.WriteLine("未找到活跃用户");
}
}
}
} // 连接会在此处自动关闭并释放
}
catch (SqlException ex)
{
// 处理数据库相关异常
Console.WriteLine($"SQL错误: {ex.Message}");
}
catch (Exception ex)
{
// 处理其他异常
Console.WriteLine($"错误: {ex.Message}");
}
finally
{
Console.WriteLine("数据库操作完成");
}
}
}
// 使用示例
class Program
{
static void Main()
{
var example = new DatabaseExample();
example.QueryData();
}
}
4.自定义实现 IDisposable 的类
模拟了一个数据库连接的场景,其中包含了非托管资源的分配、使用和释放。
代码如下(示例):
using System;
using System.Runtime.InteropServices;
// 非托管资源包装类
public class MyResourceWrapper : IDisposable
{
private IntPtr dbConnectionHandle; // 模拟数据库连接句柄
private bool disposed = false; // 标记是否已释放资源
public MyResourceWrapper()
{
Console.WriteLine("分配非托管资源...");
dbConnectionHandle = NativeMethods.OpenDbConnection();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
Console.WriteLine("手动释放资源完成");
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// 释放托管资源(如果有)
Console.WriteLine("释放托管资源");
}
// 释放非托管资源
Console.WriteLine("释放非托管资源...");
NativeMethods.CloseDbConnection(dbConnectionHandle);
dbConnectionHandle = IntPtr.Zero;
disposed = true;
Console.WriteLine("资源释放完成");
}
}
~MyResourceWrapper()
{
Console.WriteLine("垃圾回收器调用析构函数...");
Dispose(false);
}
public void ExecuteQuery(string sql)
{
if (disposed)
throw new ObjectDisposedException("MyResourceWrapper");
Console.WriteLine($"执行SQL查询: {sql}");
NativeMethods.ExecuteDbQuery(dbConnectionHandle, sql);
}
}
// 模拟本地方法
internal static class NativeMethods
{
// 模拟打开数据库连接
public static IntPtr OpenDbConnection()
{
Console.WriteLine("调用本地方法: 打开数据库连接");
// 返回一个模拟的句柄
return new IntPtr(12345);
}
// 模拟执行SQL查询
public static void ExecuteDbQuery(IntPtr handle, string sql)
{
Console.WriteLine($"调用本地方法: 执行SQL查询 - {sql}");
// 模拟查询操作
Console.WriteLine($"查询结果: 找到 {new Random().Next(1, 10)} 条记录");
}
// 模拟关闭数据库连接
public static void CloseDbConnection(IntPtr handle)
{
Console.WriteLine("调用本地方法: 关闭数据库连接");
// 实际应用中会调用本地API释放资源
}
}
// 使用示例
class Program
{
static void Main()
{
Console.WriteLine("=== 示例1: 使用using语句自动管理资源 ===");
using (var wrapper = new MyResourceWrapper())
{
wrapper.ExecuteQuery("SELECT * FROM Users");
}
// 资源会在这里自动释放
Console.WriteLine("\n=== 示例2: 手动管理资源 ===");
var manualWrapper = new MyResourceWrapper();
try
{
manualWrapper.ExecuteQuery("INSERT INTO Logs VALUES ('Test')");
}
finally
{
manualWrapper.Dispose();
}
Console.WriteLine("\n=== 示例3: 忘记释放资源(依赖GC) ===");
var leakyWrapper = new MyResourceWrapper();
leakyWrapper.ExecuteQuery("SELECT * FROM Products");
// 没有调用Dispose,资源将在GC时释放
// 注意:实际应用中不要这样做!
Console.WriteLine("\n程序继续执行...");
Console.WriteLine("提示:示例3中的资源将在GC时释放,但具体时间不确定");
}
}
5.使用IDisposable接口的代码示例
下面是一个使用 IDisposable 接口的完整代码示例,包含自定义资源管理、文件操作和数据库操作场景:
代码如下(示例):
using System;
using System.Data.SqlClient;
using System.IO;
namespace DisposableExample
{
// 自定义资源包装类,实现 IDisposable 接口
public class MyResource : IDisposable
{
private IntPtr unmanagedHandle; // 模拟非托管资源(如操作系统句柄)
private StreamWriter logFile; // 托管资源(实现了 IDisposable 的对象)
private bool disposed = false; // 资源释放状态标记
public MyResource(string logPath)
{
// 初始化非托管资源(模拟)
unmanagedHandle = IntPtr.Zero; // 实际场景中可能通过 P/Invoke 获取
// 初始化托管资源
logFile = new StreamWriter(logPath, true);
logFile.WriteLine($"资源初始化: {DateTime.Now}");
}
// 实现 IDisposable 接口的方法
public void Dispose()
{
// 调用带参数的 Dispose 方法,指定为手动释放
Dispose(true);
// 告诉 GC 无需调用终结器(Finalizer)
GC.SuppressFinalize(this);
}
// 受保护的虚方法,允许子类重写资源释放逻辑
protected virtual void Dispose(bool disposing)
{
// 检查资源是否已释放,避免重复释放
if (!disposed)
{
if (disposing)
{
// 释放托管资源
if (logFile != null)
{
logFile.WriteLine($"资源手动释放: {DateTime.Now}");
logFile.Dispose();
logFile = null;
}
}
// 释放非托管资源(模拟)
if (unmanagedHandle != IntPtr.Zero)
{
Console.WriteLine("释放非托管资源...");
// 实际场景中可能调用 Win32 API 释放句柄
unmanagedHandle = IntPtr.Zero;
}
disposed = true;
Console.WriteLine("资源已释放");
}
}
// 终结器(Finalizer),在 GC 回收对象前可能被调用
~MyResource()
{
// 仅释放非托管资源,不处理托管资源(可能已被 GC 回收)
Dispose(false);
}
// 使用资源的方法
public void DoWork()
{
if (disposed)
throw new ObjectDisposedException("MyResource");
Console.WriteLine("执行资源操作...");
logFile.WriteLine($"操作执行: {DateTime.Now}");
}
}
class Program
{
static void Main()
{
// 示例1: 使用 using 语句自动管理资源
Console.WriteLine("=== 示例1: 使用 using 语句 ===");
using (var resource = new MyResource("log.txt"))
{
resource.DoWork();
} // using 块结束时自动调用 Dispose()
// 示例2: 手动管理资源(等效于 using 语句的底层实现)
Console.WriteLine("\n=== 示例2: 手动管理资源 ===");
MyResource manualResource = null;
try
{
manualResource = new MyResource("manual_log.txt");
manualResource.DoWork();
}
finally
{
// 确保资源被释放,即使发生异常
if (manualResource != null)
manualResource.Dispose();
}
// 示例3: 数据库操作中的 IDisposable 应用
Console.WriteLine("\n=== 示例3: 数据库操作 ===");
string connectionString = "Data Source=localhost;Initial Catalog=TestDB;Integrated Security=True";
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
Console.WriteLine("数据库连接已打开");
using (var command = new SqlCommand("SELECT * FROM Users", connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"用户: {reader["UserName"]}");
}
} // 自动释放 DataReader
} // 自动释放 Command
} // 自动关闭并释放 Connection
Console.WriteLine("\n程序执行完毕");
}
}
}
三、总结
1.优先使用 using 语句:
- 代码简洁,避免内存泄漏 。
2.避免在析构函数中执行复杂操作:
- 析构函数由 GC 异步调用,可能影响性能。
- 避免访问托管对象(可能已被 GC 回收)。
3.线程安全考虑:
- Dispose() 方法通常不需要考虑线程安全(假设对象不会被多线程同时使用)。
- 若需多线程访问,需添加锁机制。
4.链式释放:
- 若类中包含其他 IDisposable 对象,需在 Dispose(bool disposing) 中调用其 Dispose()。
5.检查对象状态:
- 在公共方法中检查 disposed 标志,防止对象释放后被误用