Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
承 Item 11,雖然有 GC 在背景負責回收記憶體,幾乎不需要開發者手動處理;但是在撰寫程式時,應該避免創建過多記憶體垃圾,降低 GC 介入的次數進而減少效能消耗。
1. 將可重覆使用的區域變數提升至類別成員。
考慮以下程式碼:
protected override void OnPaint( PaintEventArgs e )
{
using ( var font = new Font( "Consolas", 12.0f ) )
{
e.Graphics.DrawString( DateTime.Now.ToString( ),
font, Brushes.Black,
new PointF( 0, 0 ) );
}
base.OnPaint( e );
}
我們可以注意到,font 這個區域變數不斷的創建以及釋放(Font 為類別);而 OnPaint 這個方法很頻繁的被呼叫,造成 heap 中的記憶體垃圾不斷產生。導致 GC 介入的次數大增。
為了改善這個情況,可以將 font 提升至類別成員,使其可以重複使用。
修改後程式碼:
private Font _font = new Font( "Consolas", 12.0f );
protected override void OnPaint( PaintEventArgs e )
{
e.Graphics.DrawString( DateTime.Now.ToString( ),
_font, Brushes.Black,
new PointF( 0, 0 ) );
base.OnPaint( e );
}
此舉讓 _font 可以被重複使用;減少 GC 介入的次數,增加效率。
2. 利用靜態成員減少相同物件創建的次數。
承 1.,.NET framework 將 Brush 設計為讓整個應用程式可共用。
public static Brush Black
{
[ResourceExposure( ResourceScope.Process )]
[ResourceConsumption( ResourceScope.Process | ResourceScope.AppDomain,
ResourceScope.Process | ResourceScope.AppDomain )]
get
{
Brush black = ( Brush ) SafeNativeMethods.Gdip.ThreadData [ BlackKey ];
if ( black == null )
{
black = new SolidBrush( Color.Black );
SafeNativeMethods.Gdip.ThreadData [ BlackKey ] = black;
}
return black;
}
}
這裡用了 Lazy initiation 的技巧,當應用程式欲取得指定顏色筆刷時;檢查字典內有無該物件,有則從字典取值後回傳、無則創建物件並加入字典後回傳。此舉減少了無用筆刷被創建的機會,進一步減少記憶體中無用的物件。
3. 使用 StringBuilder 串接複雜的字串。
考慮一般串接字串的方式。
string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString( );
此舉相當於(編譯會失敗,只是為了解釋如何運作):
string msg = "Hello, ";
string tmp1 = new string( msg + thisUser.Name );
msg = tmp1; // "Hello, " is garbage.
string tmp2 = new string( msg + ". Today is " );
msg = tmp2; // "Hello, <user>" is garbage.
string tmp3 = new string( msg + System.DateTime.Now.ToString( ) );
msg = tmp3; // "Hello, <user>. Today is " is garbage.
由於 string 為 immutable,每一次的字串串接都會產生新的字串;在這個例子中,原始字串與 tmp1, tmp2 都成為了記憶體垃圾。
string msg = $"Hello, {thisUser.Name}. Today is {System.DateTime.Now.ToString( )}";
為了減少串接字串的記憶體垃圾,使用 stringBuilder 重複利用字串。
StringBuilder msg= new StringBuilder( "Hello, " );
msg.Append( thisUser.Name );
msg.Append(". Today is " );
msg.Append( DateTime.Now.ToString( ) );
string finalMsg = msg.ToString( );
1. 盡量避免創建無用的物件,可使用輔助的程式幫忙檢查(e.g. FxCop)。
2. 將可重覆使用的區域變數(只考慮 reference type)提升至類別成員,或是靜態成員。
3. 針對 immutable type 提供 builder class。