IDisposable的使用


一、IDisposable是什么?

在 C# 里,IDisposable接口的主要功能是对非托管资源进行释放操作像文件句柄、数据库连接、网络连接这类资源,垃圾回收机制(GC)是没办法自动回收的。若不手动释放这些资源,就容易造成资源泄漏,使系统性能下降。借助实现IDisposable接口,你可以自定义资源清理的逻辑,保证资源能被及时释放

二、常见的业务应用场景

  1. 文件操作方面:在进行文件读写操作后,需要关闭文件流(FileStream、StreamReader)。

  2. 数据库交互场景:使用完数据库连接或者命令对象后,要释放它们(SqlConnection、DbCommand)。

  3. 网络通信过程:网络连接使用完毕,需将其关闭(HttpClient、TcpClient)。

  4. 图形资源处理:比如使用完Bitmap对象后要进行释放(Bitmap、Graphics)。

  5. 自定义非托管资源管理:当你封装了操作系统的一些句柄时。

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 语句:

  1. 代码简洁,避免内存泄漏 。

2.避免在析构函数中执行复杂操作:

  1. 析构函数由 GC 异步调用,可能影响性能。
  2. 避免访问托管对象(可能已被 GC 回收)。

3.线程安全考虑:

  1. Dispose() 方法通常不需要考虑线程安全(假设对象不会被多线程同时使用)。
  2. 若需多线程访问,需添加锁机制。

4.链式释放:

  1. 若类中包含其他 IDisposable 对象,需在 Dispose(bool disposing) 中调用其 Dispose()。

5.检查对象状态:

  1. 在公共方法中检查 disposed 标志,防止对象释放后被误用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值