跳過到頁腳內容
Iron Academy Logo
C# 應用程式
C# 應用程式

其他分類

IDisposable和Using語句在C#中的協同作用

Tim Corey
10m 00s

資源管理是任何C#開發者最關鍵的責任之一。 如果沒有適當的資源清理,例如檔案控制碼、資料庫連接或非受控記憶體,應用程式可能很快就會遇到性能問題、記憶體洩漏甚至系統崩潰。

在他的影片"How IDisposable and Using Statements Work Together in C#"中,Tim Corey詳細說明了C#的IDisposable模式如何確保資源管理,以及using語句如何簡化清理。在這篇文章中,我們將逐步透過他的演示來理解這種模式如何有效釋放非受控資源並防止資源洩漏。

IDisposable與資源管理簡介

Tim開始描述IDisposable為"確保您的應用程式資源管理和安全的強大工具"。他解釋說,非受控資源如資料庫连接、文件流或系統控制碼,不會由垃圾收集器自動清理。

相反,受控資源(如字串或常规的C#物件)是由垃圾收集過程自動處理的。 問題出在當一個類直接與非受控代碼或非受控資源(如操作系統級別的記憶體或文件控制碼)交互時—因為這些是超出.NET運行時的控制範圍的。

Tim強調,如果非受控資源未被明確释放,則會繼續占用,導致記憶體洩漏和系統性能變差。 IDisposable接口被設計為給開發者一個确定性的清理機制—一種在物件的生存週期結束时清理資源的保證方法。

模擬資源使用

為了展示清理的必要性,Tim創建了一個小型控制台應用程式,其中包含一個DemoResource類。 該類有一個DoWork()方法,模擬打開和關閉資料庫連接:

public class DemoResource
{
    public void DoWork()
    {
        Console.WriteLine("Opening Connection");
        Console.WriteLine("Doing Work");
        Console.WriteLine("Closing Connection");
    }
}
public class DemoResource
{
    public void DoWork()
    {
        Console.WriteLine("Opening Connection");
        Console.WriteLine("Doing Work");
        Console.WriteLine("Closing Connection");
    }
}

這代表了一個典型的涉及非受控資源的工作流程—例如與資料庫的連接或者寫入文件。DoWork()中的操作模擬假如我們直接使用非受控資源時會發生的情況。

當事情出錯時—資源洩漏

在大約2分鐘標記處,Tim演示了當過程未正確完成時會發生什麼。 他添加了一個異常來模擬操作過程中的錯誤:

throw new Exception("I broke");
throw new Exception("I broke");

當此異常發生時,程式從未到達"關閉連接"這行—這意味著非受控資源仍然打開著。

Tim回想起他的早期經歷,伺服器必須每晚重啟,因為應用程序未能關閉資料庫連接。 這些未關閉的連接會不斷累積,消耗掉所有可用的記憶體和插座。 這是由於缺少或不當的清理邏輯導致資源洩漏的經典範例。

IDisposable的作用

為了解決這個問題,Tim引入了IDisposable接口,該接口定義了Dispose方法。 實現IDisposable告訴.NET這個類有資源需要釋放,並定義這些資源應該如何釋放。

Tim將: IDisposable添加到他的類並實現該方法:

public class DemoResource : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Closing Connection via Dispose");
    }
}
public class DemoResource : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Closing Connection via Dispose");
    }
}

Dispose方法作為專用的資源清理位置,例如釋放非受控記憶體、關閉文件控制碼或釋放資料庫連接。

Tim解釋說,可以使用using語句自動調用此Dispose方法,確保即便在發生異常時也能可靠地進行清理。

Using語句與確定性清理

Tim澄清在C#中using可以意味兩种不同的事情:

  • Using指令—位於文件頂部(例如,using System;)

  • Using語句—用於資源清理

他展示了後者:

using DemoResource demo = new DemoResource();
demo.DoWork();
using DemoResource demo = new DemoResource();
demo.DoWork();

在此語句作用域結束時,編譯器會自動調用Dispose方法。 這確保了确定性的清理—意味着資源在使用後立即被釋放,而不是等到垃圾收集器稍后對物件進行完成。

這種方法通過确保所有可處置的對象在適當的時間得到正確處置,提高了應用程式的穩定性和記憶體使用效率。

當異常發生時會發生什麼

Tim重新引入异常并重新运行演示。 即使異常中斷正常流程,輸出顯示仍然調用了Dispose():

Opening Connection
Doing Work
I broke
Closing Connection via Dispose
Opening Connection
Doing Work
I broke
Closing Connection via Dispose

這表明using塊即便在出現故障時也保證清理。 這相當於將清理邏輯放在finally塊內,但更加簡潔和易讀。

這就是C# IDisposable模式的力量—它确保無論受控還是非受控資源正確釋放,而不需要在代碼的每一部分中手動清理。

使用範疇和Dispose被調用的時機

Tim接著探討了範疇如何影響釋放時機的問題。 當using聲明結束時,編譯器會自動插入調用Dispose()。

他顯示,如果您在using語句后放置另一行如:

Console.WriteLine("I'm done running Program.cs");
Console.WriteLine("I'm done running Program.cs");

此行會在Dispose()調用之前執行,因為釋放是在當前作用域退出時發生的(例如方法終了時)。

為了使釋放更早發生,Tim將代碼包裹在using塊中:

using (DemoResource demo = new DemoResource())
{
    demo.DoWork();
}
Console.WriteLine("I'm done running Program.cs");
using (DemoResource demo = new DemoResource())
{
    demo.DoWork();
}
Console.WriteLine("I'm done running Program.cs");

現在,Dispose方法在最终打印語句之前執行,因为对象在块结束时超出作用域。

這證明了确定性的資源清理,確保資源在代碼塊完成執行時立即被釋放。

完整的處置模式(擴展概念)

儘管Tim的演示側重於基礎,但它自然導向了生產代碼中使用的完整C# Dispose模式。 這個模式允許安全清理受控和非受控資源、支持繼承和避免重複清理。該模式通常看起來如下:

public class BaseResource : IDisposable
{
    private bool disposed = false; // To detect redundant calls

    // Public dispose method
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected virtual dispose method
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here
            }

            // Free unmanaged resources here
            disposed = true;
        }
    }
}
public class BaseResource : IDisposable
{
    private bool disposed = false; // To detect redundant calls

    // Public dispose method
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected virtual dispose method
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here
            }

            // Free unmanaged resources here
            disposed = true;
        }
    }
}

這裡發生了什麼:

  • Dispose(bool disposing)區分處理受控對象(當disposing為true時)和釋放非受控資源(總是需要)。

  • disposing參數有助於在析構函數期間避免處置受控對象,因為垃圾收集器可能已經回收了它們。

  • GC.SuppressFinalize(this)阻止垃圾收集器在手動處理后調用析構函數。

  • protected virtual void Dispose(bool disposing)允許派生類使用protected override void Dispose(bool disposing)覆蓋處置行為以進行連鎖處置呼叫。

這確保了高效的資源管理,防止資源洩漏,並为受控和非受控資源提供安全的清理邏輯。

為什麼妥善清理很重要

Tim的範例強調正確實現Dispose模式的重要性—不僅僅是為了關閉資料庫連接,還為了優雅地處理非受控記憶體、文件控制碼和系統資源。 通過實現IDisposable並將對象包裹在using語句中,您可以確保:

  • 資源被及時釋放

  • 垃圾回收不需要處理非受控資源

  • 記憶體使用保持最佳

  • 應用程式保持穩定和高效

結論

正如Tim在他的影片中總結的那樣,IDisposable接口和using語句合作無間,以保證即使在發生異常時也能自動進行清理。

通過實現Dispose模式,您可以完全控制對象如何釋放其受控和非受控資源,同時using塊確保此過程在正确時刻触发—不论發生什麼。

這種組合形成了有效資源管理的骨幹,在C#中確保穩定、高效且無洩漏的應用程式。

"當您在使用語句中使用IDisposable時,Dispose方法將始終在作用域結束时被調用—不論是否有異常。" —Tim Corey

簡言之,了解並實踐C# IDisposable模式是掌握資源清理、預防洩漏和提升應用程式穩定性的必要步驟。

Hero Worlddot related to IDisposable和Using語句在C#中的協同作用
Hero Affiliate related to IDisposable和Using語句在C#中的協同作用

通過分享您所愛的東西賺得更多

您是否在為使用.NET、C#、Java、Python或Node.js的開發者創建內容?將您的專業知識轉化為額外收入!

鋼鐵支援團隊

我們每週 5 天,每天 24 小時在線上。
聊天
電子郵件
打電話給我