Chapter 2 - Item 11 : Understand .NET Resource Management

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

C# CLR 環境中有 GC 負責回收記憶體;在大部分情況下,並不需要由開發者手動釋放記憶體。即便 GC 讓開發過程很方便,還是有少數情況需要開發者自行處理,如:

    1. Database Connection.
    2. GDI+ objects.
    3. COM Objects.
    4. Other system objects.
    5. Event handler or delegates links between two objects.
    6. Query requests.

以下介紹 GC 如何運作,以及 C++ 與 C# 回收機制有何不同,最後探討 Finalizer(解構子)帶來的效能負面影響。

1. GC Collect 包含了回收與壓縮記憶體。


   
    GC Collect 執行步驟:
        a. 暫止所有執行緒。
        b. 掃描沒有被 root 參考的物件。
            - 沒有實作 Finalizer → 回收記憶體。
            - 有實作 Finalizer → 將物件加入 Freachable Queue 中,非同步執行 Finalizer。
        c. 壓縮可用記憶體,讓可用記憶體為連續空間(增加使用效率)。

2. C++ 與 C# Finalizer 代表的意義不同。    

// Good in C++, bad in C#
internal class CriticalSection
{
    // Constructor acquires the system resource.
    public CriticalSection( )
    {
        enterCriticalSection( );
    }

    private void enterCriticalSection( )
    {
    }

    private void exitCriticalSection( )
    {
    }

    // Destructor releases system resource.
    // Finalizer
    ~CriticalSection( )
    {
        exitCriticalSection( );
    }
}

// C++ usage
void func( )
{
    // The lifetime of section controls assess to the system resource.
    CriticalSection section = new CriticalSection( );

    // Do some Work.

    // Compiler generates call to destructor, but not in C#.
    // Code exits critical section.
}

C++ Finalizer 在離開 func 後,會立刻執行 Finalizer;而 C# 則無法確定執行時間點,並由 GC 執行的時間點而定。

結論:
1. C# Finalizer 視為物件釋放 Unmanage resource 的最後一道防線,但濫用 Finalizer 將會造成性能上的損失(增加記憶體垃圾存活的時間)。

2. 每次 GC Collect 皆會掃描 gen 0 物件。粗略來說,10 次 GC cycle 會掃描 1 次 gen 1 物件;gen 2 則是 100 次。也就是說,如果有實作 Finalizer 的物件垃圾在 gen 0 被發現,該物件接著會被提升到 gen 1 且非同步執行 Finalizer;該物件仍會存活至少 9 次的 GC cycle 才會被回收。

3. 實作 IDisposable 介面,藉由明確呼叫 Dispose 消除 Finalizer 帶來效能上的負面影響。