# 原則16:避免創建不需要的對象
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
垃圾回收期在管理內存方面非常出色,它非常高效地移除不再使用的對象。但是無論你如何看待它,分配和銷毀一個基于堆內存的對象花費處理器時間比分配和銷毀不是基于堆內存的對象要多。在函數內創建大量的引用類型對象會引入嚴重的性能消耗問題。
所以不能讓垃圾回收器超負荷工作。你可以借鑒一些簡單的技巧最小化垃圾回收器的工作。所有的引用類型對象,即使是局部變量,都被分配存儲在堆內存上。每個引用類型的局部變量在函數結束都會變為垃圾。一個最常見的壞實踐是在 Windows 畫圖處理申請 GDI 對象:
```
// Sample one
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);
}
```
OnPainr() 會被頻繁調用。每次被調用,你都會創建另一個包含相同設置的 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);
}
```
你的程序不會每個繪畫事件都產生垃圾。垃圾回收器的負擔更少。你的程序也會跑的更快一點。當你把一個實現 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 類保持黑色畫刷的一個簡單的引用,當你再需要的時就會返回這個引用。結果就是你只創建了一個黑色畫刷并且可以一直重復使用。并且,如果你程序不需要穿件一個特殊的資源——比如,檸檬綠畫刷——它就不會被創建。框架提供方法限制了對象的創建,在你完成目標的情況下使用最少的對象集。學會在你的程序中使用這樣的技巧。
你已經學會兩種技巧減少申請對象的數量,就像完成自己的業務一樣。你可以將經常使用的局部變量提升為成員變量。你也可以使用單例模式讓一個類提供常用的實例。最后這種技巧還包含了不可變類型的最終值。 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);
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 以及開始創建的消息(“Hello”),都是垃圾。string 類的 += 方法會創建新的字符串并返回。在字符串上連接字符不會修改原來的字符串。像前面的構造,可以使用 string.Format() 方法:
```
string msg = string.Format("Hello, {0}. Today is {1}",thisUser.Name, 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();
```
StringBuilder 是一個可變字符串類用于構建不可變的 string 對象。在形成不可變 string 對象之前,它提供了配套的可變字符串方法去創建和修改文本內容。使用 StringBuilder 創建最終版本的字符串對象。更重要的是,學會這種設計思想。考慮當你設計不可變類型時(查看原則20),考慮創建一個對象去構建需要多次構造而成的最終對象。可以讓使用者一步一步構建對象,并且維護這個不可變的類型。
垃圾回收器高效地管理你程序使用的內存。但是記住創建和銷毀堆內存對象仍會花費時間。避免創建大量對象,不要創建你不需要的對象。同時避免創建在函數內部創建多個引用類型對象。相反,考慮提升局部變量為成員變量,或者為你的類型創建大多數常用的實例。最后,考慮創建一個可變類來構建不可變類。
小結:
這則原則相對簡短,但是非常重要,尤其是對于菜鳥碼農,總之,避免重復創建對象。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2078554](/blog/2078554)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數