# 原則10:使用默認參數減少函數的重載
**By D.S.Qiu**
尊重他人的勞動,**支持原創,轉載請注明[出處](/blog/1986910):[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
C# 現在已經在函數調用上支持命名參數了。這意味著名字形式的參數是你的類 public 接口的一部分。改變 public 參數的名字將破壞函數調用代碼。這意味著很多時候要避免使用命名參數,或者避免改變 public 或 protected 方法的命名參數的名字。
當然,沒有一個語言設計者增加的特性會增加你的難度。命名參數和默認參數防止了很多 API 的多而雜亂,特別是 Microsoft Office 的 COM API 。下面這段代碼使用 COM 類的方法創建 Word 文檔并插入一小段文本:
```
var wasted = Type.Missing;
var wordApp = new Microsoft.Office.Interop.Word. Application();
wordApp.Visible = true;
Documents docs = wordApp.Documents;
Document doc = docs.Add(ref wasted, ref wasted, ref wasted, ref wasted);
Range range = doc.Range(0, 0);
range.InsertAfter("Testing, testing, testing. . .");
```
這段代碼使用了小而沒用的 Type.Missing 對象次數。很多 Office Interop 應用都會使用大量的 Type.Missing 對象。這些實例弄混了你的應用并隱藏了你構建軟件的實際邏輯。
在 C# 語言中,這個混亂只要是因為添加了默認參數和命名參數導致的。默認參數意味著意味著這些 Office API 可以使用默認值對那些所有使用 Type.Missing 的地方。簡化上面的小段代碼:
```
var wordApp = new Microsoft.Office.Interop.Word.Application();
wordApp.Visible = true;
Documents docs = wordApp.Documents;
Document doc = docs.Add();
Range range = doc.Range(0, 0);
range.InsertAfter("Testing, testing, testing. . .");
```
命名參數的作用是在任何有默認參數的 API 中,你只需要制定你想要使用的參數。這個簡單并省去了多次重載。實際上,四個不同參數,你需要重載15個不同的 Add() 來達到命名參數和默認參數相同的靈活性。記住一些 Office API 有多達16個參數,默認參數和命名參數就是一個很大幫助。
我在參數列表使用 ref 修飾符,但在 C# 4.0的另一個改變就是在 COM 中這是默認的。這是因為 COM ,一般來說,使用對象引用來傳遞參數,所有幾乎所有參數都是按引用來傳遞參數的,即使函數內沒有對參數進行改變。實際上, Range() 調用通過引用傳入值(0,0)的。我們沒有包含 ref 修飾符,因為那將是明顯的誤導。實際,在多少代碼中,我們都沒有包含 ref 修飾符去調用 Add() 。上面我就是這樣做的,你可以去查看下實際的 API 簽名。
當然,僅僅是因為 COM 和 Office API 的命名參數和默認參數,這不意味著你需要限制使用它們在 Office Interop 應用中。實際上,這是不能的。開發者使用命名參數調用你的 API 無論你是否希望他們這么做。
下面的方法:
```
private void SetName(string lastName, string firstName)
{
//elided
}
```
使用命名參數調用避免命名參數次序的混亂:
```
SetName(lastName: "Wagner", firstName: "Bill");
```
注解命名參數保證別人后面看這段代碼不用去想參數的位置是否正確。開發者使用命名參數將閱讀代碼的人的更清晰。當調用的函數有多個相同類型的參數,使用命名參數會使你的代碼更加具有可讀性。
改變參數名字的破壞會表現得很有趣。在 MSIL 中參數名字存儲在函數調用點,而不是函數入口。你可以改變參數名字并且發布的組件不會破壞任何使用這個組件的代碼。使用這個組件的開發者會有一個重要的改變當需要重新編譯升級版本,但之前的版本的客戶端代碼仍然可以正確運行。所以你至少不會破壞已存在的程序集。使用你代碼的開發者會懊惱,但是不會對你抱怨出現的問題。例如,假設你修改 SetName() 的參數名字:
```
public void SetName(string Last, string First);
```
你將會編譯和發布這個程序集作為一個補丁。任何調用這個方法的程序集都能繼續運行,即使包含了指定參數名字的 SetName 的調用。然而,當客戶端開發者想要升級他們的程序集,像下面的代碼都不會通過編譯:
```
SetName(lastName : "Wanger",firstName : "Bill");
```
改變默認參數的值童謠需要調用者重寫編譯保證這個更新一致。如果你編譯你的程序集并發布這個補丁,所有已存在的調用將繼續使用之前的默認值。
當然,你也不會隨意的對使用你的組件的開發者帶來麻煩。所以,你必須把參數的名字當做組件 public 接口的一部分。改變參數名字在客戶端代碼再編譯時會有問題。
此外,增加參數(即使是默認參數)都會在運行時有問題。默認參數的實現跟命名參數是類似的。調用點會包含 MSIL 用于反射出默認值的存在和具體值時多少的注解。函數入口替換調用者沒有明確指定的默認參數的值。
所以,即使增加一個默認參數,都會在運行時出現問題。如果它們有默認參數,在編譯時不會有問題。
至此,經過一番解釋,這個指導應該更加清晰了。為了初始版本的發布,使用默認參數和命名參數可以創造使用者想要得到重載組合。但是,一旦你創建未來的版本,你必須創建額外參數的重載。那樣已經存在的客戶端程序會繼續有效。此外,在任何將來的版本中,避免更改參數名稱。它們現在是你的 public 接口的一部分。
小結:
這篇跨了一個星期,因為自己脖子到現在還是疼的,然后公司搬家,這個星期脖子一直都很痛,加上上班的工作任務也重,會到家就直接洗澡休息了,連續休息了一周感覺還是不錯的,至少臉上的“平靜”了很多,氣色也好了很多,但是又覺得很不心安,感覺荒廢了,我要加油,繼續努力。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/1986873](/blog/1986873)
更多精彩請關注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<T> 和 IComparer<T> 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數