Effective C# Item 16: Minimize Garbage
在我們使用C#時,GC(Garbage Collector)會非常好的協助我們完成對記憶體資源的管理,它會非常高效的清理沒有存在意義的對象。但是不管怎麼說,即便再高效的工作,頻繁的分配和銷毀堆中的對象也會降低效率。如果我們在程式中建立了大量的參考型別,那將會在一定程度上影響到執行的效率。
因此我們不要讓GC過度工作。我們可以使用一些簡單的方法來盡量減少GC在我們程式中的操作。所有的參考型別都在堆上分配記憶體。當一個函數調用結束時,其中的局部變數就會變成垃圾。下例是一個反面典型:
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);
}
OnPaint()方法被調用非常頻繁,每次調用時就會建立一個Font對象,而這個對象的內容卻是相同的。GC需要在每次調用結束後清理這些資源,這就降低了效率。
我們可以將Font對象由局部變數改為成員變數,這樣我們就不必每次都重新建立它了。
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);
}
現在我們的程式在調用Paint時不會再產生不必要的垃圾了,GC做的工作也相應減少了,效率也就提升了。當我們將局部變數轉變為成員變數以減少垃圾的時候,我們應該實現該物件類型的IDisposable介面。讓我們可以在最後釋放它的資源。
當我們的局部變數是參考型別而且調用頻繁的時候,我們就應當將其提升為成員變數。Font就是一個很好的例子。不過只有在頻繁被調用時候才這樣做。我們的目的是減少垃圾,而不是說要將所有的局部變數變為成員變數。
避免建立重複對象的另一個方法是使用靜態成員,一個例子就是Burshes.Black。每次當我們在繪製視窗的時候就要用到它。如果我們每次都要建立一個新的Brush,那麼我們就需要銷毀非常多的重複Brush。因此.Net Framework的設計者便建立了一個Brushes類,其中包含了很多的Brush的靜態對象。
private static Brush _blackBrush;
public static Brush Black
{
get
{
if(_blackBrush == null)
{
_blackBrush = new SolidBrush(Color.Black);
}
return _blackBrush;
}
}
當我們第一次需要一個黑色的Brush時,Brushes類會建立它。這個黑色的Brush會一直被我們使用下去。而且對於我們不需要的Brush,例如綠色的,永遠也不會被建立。這樣我們可以付出最少的代價來得到想要的結果。
上面提到了兩種減少垃圾出現的辦法,一個是將局部變數提升為成員變數,一個是通過聲明類的靜態成員。還有一種方法是為不可變類型提供其最終值,而不要頻繁修改它。System.String就是一個不可變類型。當我們構建了string之後,它的內容就是不修改的了。當我們修改string時,其實是建立了另外一個string的對象,而原來的對象就變成了垃圾:
string msg = "Hello,";
msg += thisUser.Name;
msg += ". Today is";
msg += System.DateTime.Now.Tostring();
上例中的做法等同於下面的代碼:
string msg = "Hello,";
//這樣聲明是非法的,僅樣本
string temp1 = new String(msg + thisUser.Name);
msg = temp1;
string temp2 = new String(msg + ". Today is";);
msg = temp2;
string temp3 = new String(msg + System.DateTime.Now.ToString());
msg = temp3;
tmp1,tmp2,tmp3和最初建立的msg都便成了垃圾。+=操作在string類上會建立一個新的string進行修改,而並不是修改已經存在的對象。我們可以使用Format()方法來建立string:
string msg = string.Format("Hello, {0}.Today is {1}", thisUser.Name, DateTime.Now.ToString());
對於更為複雜的string的建立,我們可以使用StringBuilder類:
StringBuilder msg = new StringBuilder("Hello, ");
msg.Append(thisUser.Name);
msg.Append(". Today is ");
msg.Append(DateTime.Now.ToString());
string finalMsg = msg.ToString();
StringBuilder是一個可變類型,它可以建立不可變的string類型。它提供了方便修改的可變字串來讓我們進行建立和修改工作。更重要的是,它可以為我們提供一些類的設計上的思路。當我們建立一個不可變類型時,也應當考慮為其建立一個構造器來完成頻繁修改的工作並返回目標對象。
GC雖然工作效率很高,但是建立和銷毀堆上的對象依然要消耗時間。我們應當避免建立無用的對象,避免建立大量重複的對象。最後還應當注意不可變類型的建立和修改。
譯自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目錄