Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

การนำ IDisposable Pattern ไปใช้ใน C# (Dispose Pattern)

IDisposable Pattern (Dispose Pattern) เป็นมาตรฐานสำหรับปล่อยทรัพยากร Unmanaged Resources (File Handles, Database Connections, Network Streams, COM Objects) อย่างถูกต้องและปลอดภัย

เมื่อไหร่ต้อง Implement IDisposable?

  • คลาสครอบ Unmanaged Resources โดยตรง (IntPtr, SafeHandle)
  • คลาสมี Field ที่เป็น IDisposable (Managed Resource ที่ครอบ Unmanaged)
  • Base Class Implement IDisposable (Derived ต้อง Override)

Standard Dispose Pattern (Full Pattern)

public class ResourceWrapper : IDisposable
{
    // 1. Unmanaged Resource (เช่น Handle, Pointer)
    private IntPtr _unmanagedHandle;
    
    // 2. Managed Resource ที่ Implement IDisposable
    private readonly FileStream _fileStream;
    
    // 3. Flag ตรวจสอบ Dispose แล้วหรือยัง
    private bool _disposed = false;
    
    public ResourceWrapper(string filePath)
    {
        _fileStream = File.OpenRead(filePath);
        _unmanagedHandle = _fileStream.SafeFileHandle.DangerousGetHandle(); // ตัวอย่าง
    }
    
    // 4. Public Dispose() - เรียกได้จากภายนอก
    public void Dispose()
    {
        Dispose(true);        // 5. เรียก Overload ที่ทำงานจริง
        GC.SuppressFinalize(this); // 6. บอก GC ว่าไม่ต้องเรียก Finalizer
    }
    
    // 7. Protected Virtual - ให้ Derived Class Override ได้
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return; // Guard Clause
        
        if (disposing)
        {
            // 8. Dispose Managed Resources (เรียก Dispose ของ Fields)
            _fileStream?.Dispose();
            // managedResource?.Dispose();
        }
        
        // 9. Free Unmanaged Resources (เสมอ - ทั้ง Disposing และ Finalizing)
        if (_unmanagedHandle != IntPtr.Zero)
        {
            // CloseHandle(_unmanagedHandle); // P/Invoke
            _unmanagedHandle = IntPtr.Zero;
        }
        
        _disposed = true;
    }
    
    // 10. Finalizer (Destructor) - เรียกโดย GC ถ้าลืม Dispose()
    ~ResourceWrapper()
    {
        Dispose(false); // false = ไม่ Dispose Managed Resources (อาจถูก GC แล้ว)
    }
    
    // ใช้งาน Resource
    public void ReadData()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(ResourceWrapper));
        // ...
    }
}

การใช้งานกับ using Statement (แนะนำ)

// using เรียก Dispose() อัตโนมัติ แม้จะมี Exception
using (var wrapper = new ResourceWrapper("data.txt"))
{
    wrapper.ReadData();
} // Dispose() ถูกเรียกที่นี่

รูปแบบย่อสำหรับ Managed Resources เท่านั้น (No Unmanaged)

public class SimpleWrapper : IDisposable
{
    private readonly DbContext _context;
    private bool _disposed;
    
    public SimpleWrapper() => _context = new DbContext();
    
    public void Dispose()
    {
        if (!_disposed)
        {
            _context?.Dispose();
            _disposed = true;
        }
    }
    
    // ไม่ต้องมี Finalizer หากไม่มี Unmanaged Resources
}

Best Practices

  • ใช้ SafeHandle (CriticalHandle) แทน IntPtr สำหรับ Unmanaged Handles
  • ทำให้ Dispose() เป็น Idempotent (เรียกซ้ำได้ไม่ Error)
  • โยน ObjectDisposedException หากใช้หลัง Dispose
  • Derived Class: Override Dispose(bool) เรียก base.Dispose(disposing) ท้ายสุด
  • ใน .NET Core/5+: ใช้ IAsyncDisposable สำหรับ Async Cleanup

ตัวอย่าง Derived Class

public class DerivedWrapper : ResourceWrapper
{
    private readonly MemoryStream _memoryStream;
    
    public DerivedWrapper(string filePath) : base(filePath)
    {
        _memoryStream = new MemoryStream();
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _memoryStream?.Dispose(); // Dispose Own Managed Resources
        }
        base.Dispose(disposing); // เรียก Base สุดท้าย
    }
}