程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則16:垃圾最小化

Effective C#原則16:垃圾最小化

編輯:關於C#

垃圾回收器對內存管理表現的非常出色,並且它以非常高效的方法移除不再 使用的對象。但不管你怎樣看它,申請和釋放一個基於堆內存的對象總比申請和 釋放一個不基於堆內存的對象要花上更多的處理器時間。你可以給出一些嚴重的 性能問題,例如應用程序在某個方法內分配過量的引用對象。

你不應該 讓垃圾回收器超負荷的工作,為了程序的效率,你可以使用一些簡單的技巧來減 少垃圾回收器的工作。所有的引用類型,即使是局部變量,都是在堆上分配的。 所有引用類型的局部變量在函數退出後馬上成為垃圾,一個最常見的“垃 圾”做法就是申請一個Windows的畫圖句柄:

protected override void OnPaint( PaintEventArgs e )
{
 // Bad. Created the same font every paint event.
 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對象,而實際上它是完全一樣的內容。垃圾回收器每次都須 要清理這些對象。這將是難以置信的低效。

取而代之的是,把Font對象 從局部變量提供為對象成員,在每次繪制窗口時重用同樣的對象:

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 );
}

這樣你的程序在每 次paint事件發生時不會產生垃圾,垃圾回收器的工作減少了,你的程序運行會 稍微快一點點。當你把一個實現了IDisposable接口的局部變量提升為類型成員 時,例如字體,你的類同樣也應該實現IDisposable接口。原則18會給你解釋如 何正確的完成它。

當一個引用類型(值類型的就無所謂了)的局部變量在 常規的函數調用中使用的非常頻繁時,你應該把它提升為對象的成員。那個字體 就是一個很好的例子。只有常用的局部變量頻繁訪問時才是很好的候選對象,不 是頻繁調用的就不必了。你應該盡可能的避免重復的創建同樣的對象,使用成員 變量而不是局部變量。

前面例子中使用的靜態屬性Brushes.Black,演示 了另一個避免重復創建相似對象的技術。使用靜態成員變量來創建一些常用的引 用類型的實例。考慮前面那個例子裡使用的黑色畫刷,每次當你要用黑色畫刷來 畫一些東西時,你要在程序中創建和釋放大量的黑色畫刷。前面的一個解決方案 就是在每個期望黑色畫刷的類中添加一個畫刷成員,但這還不夠。程序可能會創 建大量的窗口和控件,這同樣會創建大量的黑色畫刷。.Net框架的設計者預知了 這個問題,他們為你創建一個簡單的黑色畫刷以便你在任何地方都可以重復使用 。Brushes對象包含一定數量的靜態Brush對象,每一個具有不同的常用的顏色。 在內部,Brushes使用了惰性算法來,即只有當你使用時才創建這些對象。一個 簡單的實現方法:

private static Brush _blackBrush;
public static Brush Black
{
 get
 {
  if ( _blackBrush == null )
   _blackBrush = new SolidBrush( Color.Black );
   return _blackBrush;
 }
}

當你第一次申請黑色畫刷時,Brushes類就會創建它。然而 Brushes類就保留一個單一的黑色畫刷的引用句柄,當你再次申請時它就直接返 回這個句柄。結果就是你只創建了一個黑色畫刷並且一直在重用它。另外,如果 你的應用程序不須要一個特殊的資源,一個檸檬綠(lime green)的畫刷就可能永 遠不會創建。框架提供了一個方法來限制對象,使得在滿足目標的情況下使用最 小的對象集合。學會在你的應用程序裡使用這樣的技巧。

你已經學會了 兩種技術來最小化應用程序的(對象)分配數量,正如它承擔它自己的任務一樣。 你可以把一個經常使用的局部變量提升為類的成員變量,你可以提供一個類以單 件模式來存儲一些常用的給定對象的實例。最後一項技術還包括創建恆定類型的 最終使用值。System.String類就是一個恆定類型,在你創建一個字符串後,它 的內容就不能更改了。當你編寫代碼來修改這些串的內容時,你實際上是創建了 新的對象,並且讓舊的串成為了垃圾。這看上去是清白的例子:

string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString();

這實際上低效的如果你是這 樣寫:

string msg = "Hello, ";
// Not legal, for illustration only:
string tmp1 = new String( msg + thisUser.Name );
string msg = tmp1; // "Hello " is garbage.
string tmp2 = new String( msg + ". Today is " );
msg = tmp2; // "Hello <user>" is garbage.
string tmp3 = new String( msg + DateTime.Now.ToString( ) );
msg = tmp3;// "Hello <user>. Today is " is garbage.

字符串tmp1,tmp2,tmp3以及最原始的msg構造的 (“Hello”),都成了垃圾。+=方法在字符串類上會生成一個新的對 象並返回它。它不會通過把字符鏈接到原來的存儲空間上來修改結果。對於先前 這個例子,給一個簡單的構造例子,你應該使用string.Format()方法:

string msg = string.Format ( "Hello, {0}. Today is {1}",
 thisUser.Name, DateTime.Now.ToString( ));

對於更多的復雜的字符串操作,你應該使用StringBuilter類 :

StringBuilder msg = new StringBuilder( "Hello, " );
msg.Append( thisUser.Name );
msg.Append( ". Today is " );
msg.Append( DateTime.Now.ToString());
string finalMsg = msg.ToString();

StringBuilder也一個( 內容)可變的字符串類,用於生成恆定的字符串對象。在你還沒有創建一個恆定 的字符串對象前,它提供了一個有效的方法來存儲可變的字符串。更重要的是, 學習這樣的設計習慣。當你的設計提倡使用恆定類型時(參見原則7),對於一些 要經過多次構造後才能最終得到的對象,可以考慮使用一些對象生成器來簡化對 象的創建。它提供了一個方法讓你的用戶來逐步的創建(你設計的)恆定類型,也 用於維護這個類型。

(譯注:請理解作者的意圖,只有當你使用恆定類型 時才這樣,如果是引用類型,就不一定非要使用對象生成器了。而且注意恆定類 型的特點,就是一但創建就永遠不能改變,所有的修改都會產生新的實例, string就是一個典型的例子,它是一個恆定的引用類型;還有DateTime也是一個 ,它是一個恆定的值類型。)

垃圾回收器在管理應用程序的內存上確實很 高效。但請記住,創建和釋放堆對象還是很占時間的。避免創建大量的對象,也 不要創建你不使用的對象。也要避免在局部函數上多次創建引用對象。相反,把 局部變量提供為類型成員變量,或者把你最常用的對象實例創建為靜態對象。最 後,考慮使用可變對象創建器來構造恆定對象。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved