這個做法提出了建立過多物件會影響效率的概念,雖然我們的系統有 GC 能夠高效的管理記憶體中的物件, 但清除的效率在快還不如不創建無意義的物件,從根本上解決過度使用 GC 的導致的效率問題。
在做法 11 中有提到 GC 的基礎觀念,GC 就是在管理 Heap 記憶體空間,所以要減少 GC 執行的次數,等同於要盡量減少 在 Heap 中分配記憶體的相關操作,也就是所有的參考型別都包含在內。
以下面這段程式碼為例,假設 OnPaint 經常被呼叫的話,相同的 Font 物件就會不斷被創建並且結束後呼叫 Dispose
方法釋放掉記憶體。
public class Draw
{
protected override void OnPaint(PaintEventArgs e)
{
using (Font MyFont = new Font("Arial", 10.0f))
{
e.Graphics.DrawString(DateTime.Now.ToString(),
MyFont, Brushes.Black, new PointF(0, 0));
}
base.OnPaint(e);
}
}
所以有一個技巧是把 Font 物件從區域變數改成成員變數,這樣就只需要初始化一次就能重複使用 Font 物件了,不過這種做法會導致 Font 物件 不會即時釋放掉,所以要把這個類別也實做 IDisposable 才能正確釋放掉。
public class Draw : IDisposable
{
private readonly Font myFont = new Font("Arial", 10.0f);
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(DateTime.Now.ToString(),
myFont, Brushes.Black, new PointF(0, 0));
base.OnPaint(e);
}
public void Dispose()
{
myFont.Dispose();
...
}
}
上面的做法只適用於非常常用的物件才建議這麼處理,並不代表要把所有區域變數改成成員變數,一般的物件還是使用第一種 using
方法直接釋放掉就好。
第二個技巧是將物件改成靜態,靜態成員在前幾個做法有提到,我們可以透過它只會共用的特性來避免建立過多重複的物件,例如剛剛的程式碼裡面有個
Brushes.Black
取得筆刷的屬性就是個靜態的屬性。
public static Brush Black => GetBrush(s_blackKey, Color.Black);
還有一個是 System.String 類別使用上要注意的問題,在下面的寫法中雖然看起來是將資料拼接到同一個變數,但實際上 System.String 類別是 immutable 代表建構出字串後就不能再次修改了,所以我們下面的寫法實際上是不斷建立新的字串並且取代掉舊的,所以背後建立了多個垃圾變數。
void Main()
{
string msg = "Hello";
msg += ". Today is ";
msg += System.DateTime.Now.ToString();
Console.WriteLine(msg);
}
所以需要改用字串插值或者 StringBuilder
來減少背後變數建立的數量。
void Main()
{
string msg = string.Format("Hello. Today is {0}", DateTime.Now.ToString());
Console.WriteLine(msg);
}
void Main()
{
StringBuilder msg = new StringBuilder("Hello, ");
msg.Append(". Today is ");
msg.Append(DateTime.Now.ToString());
string finalMsg = msg.ToString();
Console.WriteLine(msg);
}
Summary
這個做法主要核心是不要建立過多個參考型別變數,因為會導致 GC 運作過於頻繁,並且提出兩種辦法讓變數盡量少創建,不過可能會導致記憶體釋放
不夠即時,最後是 System.String 是一個不變的類型,所以在修改時背後會建立多個隱藏的變數,使用字串插值或 StringBuilder
可以解決相關問題。