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 帶來效能上的負面影響。