《Effective C#》讀書筆記——條目16:避免創建非必要的對象<.NET資源管理>


  我們知道:C#是一門虛擬機語言,C#編譯器首先將C#代碼編譯成IL代碼,運行程序時CLR(Common Language Runtime,公共語言運行時)通過調用JIT(just-in-time Compiler即時編譯器)來將IL代動態即時編譯成可執行的機器碼。GC(Garbage Collector,垃圾收集器)自動為我們的應用程序進行內存管理的分配和釋放,(具體參見:了解.NET 內存管理機制),以一種高效的方式來移除內存中的垃圾對象,不過不管有多高效,分配和銷毀在堆上的對象總會花費掉時間。

  如果我們在一個方法中創建了過多的引用對象,會對應用程序的性能產生嚴重的影響。因此我們應該遵守下面的一些規則,可以盡量的降低GC的工作量。

 

閱讀目錄:

      1.將常用的局部變量提升為成員變量

      2.為常用的類型實例提供靜態對象

      3.為不可變類型提供可變的創建對象

      小節

 

1.將常用的局部變量提升為成員變量

   所有的引用類型,包括那些局部變量,都會分配到堆上。在函數退出后,函數內的所有局部變量都會立即變成垃圾對象。所以我們可以得出結論:

若是某個引用類型(值類型無所謂)的局部變量用於被頻繁調用的例程中,那么應該將其提升為成員變量。這既有助於減輕GC的負擔,也可以提升程序運行的效率。

 在GUI編程中一個常見的錯誤就是:在窗體的Paint處理函數中創建GDI(Graphics Device Interface,圖形設備接口)對象,如下:

1         protected override void OnPaint(PaintEventArgs e)
2         {
3             using (Font myFont = new Font("Arial", 10.0f))
4             {
5                 e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new Point(0, 0));
6             }
7             base.OnPaint(e);
8         }

 

OnPaint()將會被非常頻繁的調用,每次調用都會創建一個Font對象,而包含的內容完全和上一次一樣。所以GC需要每次都為你清掃這些垃圾,嚴重影響了應用程序的效率。其實我們完全可以將Font對象提升為成員變量,是每次窗體重繪時能夠重用該Font對象:

1         private readonly Font myFont = new Font("Arial", 10.0f);
2 
3         protected override void OnPaint(PaintEventArgs e)
4         {
5                 e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new Point(0, 0));
6 
7                 base.OnPaint(e);
8         }

 將常用的局部變量提升為成員變量之后,程序無需每次重繪時生成垃圾對象,減輕了GC的負擔,也提升了程序的效率。不過這里有一個小小的限制:將實現了IDisposable接口的局部變量提升為成員變量,那么這個成員變量依附類本身也需要實現IDisposable接口。

 

2.為常用的類型實例提供靜態對象

  靜態成員變量可以讓引用類型在類的各個實例中共享。我們可以通過提供了一個類,存放某個類型常用的實例的單例對象,這樣可以避免創建重復的對象。.NET Framework 的類庫中就有很多這樣的做法:Brushes類包含了一系列的靜態Brush對象,每個都包含了一種常用的顏色。它們的簡要實現如下:

 1         private static Brush blackBrush;
 2         public static Brush Black
 3         {
 4             get
 5             {
 6                 if (blackBrush == null)
 7                     blackBrush = new SolidBrush(Color.Black);
 8                 return blackBrush;
 9             }
10         }

在第一次請求黑色畫刷時,Brushes將創建一個實例,隨意Brushes類將保留該實例的引用,並在后續的請求時直接返回同一個句柄。也就是說我們只創建了一個黑色畫刷,然后一直重用這個對象。至於其他的例如:紅色的畫刷,如果應用程序沒有使用這個資源那么該對象也不會被分配。.NET 提供的這種方式能夠在滿足需求的前提下盡可能的少創建對象,我們在自己的應用程序中也應該這樣做。

 

3.為不可變類型提供可變的創建對象

  System.String類型時一個不可變類型:即在構造一個字符串對象后,其內容不能被修改。如果對一個字符串進行修改時,實際上時創建了一個新的字符串對象,從前的字符串對象也就變成了垃圾。看下面的代碼:

1             //How are you?
2             string msg = "How";
3             msg += " are";
4             msg += " you";
5             msg += "?";

 

string類型的+=操作符會創建一個新的字符串對象並返回,對於這類拼接字符串的工作應該交給更適合的string.Format()方法:

1 string msg = string.Format("{0} {1} {2}{3}", "How", "are", "you", "?");

 

如果需要進行一些比簡單拼接字符串更加復雜的工作,可以考慮使用StringBuilder類,該類是一個可變的字符串,用來創建不可變的string對象。對於經常變化的stirng對象,使用StringBuilder對象來替換是一個非常好的選擇 —— 當我們的某個設計需要不可變類型時,應該考慮提供一個創建對象,專門負責分步地構造出最終對象(例如:string對象之於StringBuilder對象)。這樣既可讓用戶分步地創建出最終對象,也可以保證對象的不可變性

 

小節

  GC可以高效的管理應用程序使用的內存,不過創建和銷毀堆上的對象仍舊需要時間,因此,應該避免創建過多的對象,不要創建那些非必要的對象,也不要在局部方法中創建太多的引用對象,可以考慮將常用的局部變量提升為成員變量,或者為最常用的類型實例提供靜態對象,此外還可以考慮為不可變類型提供可變的創建對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM