標籤:
一、背景:互連網行業,為了降低程式維護、升級的部署風險,往往會將程式拆分成很多項目,編譯成多個dll部署,這樣發布的時候,只需要部署修改過的dll即可。
二、問題:有一個函數,在很多個地方被使用:
public fun1(A a ,B b){ //代碼主體}突然有一天,有的地方調用的時候需要加入一個參數C c,但是又不想其他客戶程式有任何變動,可以充分利用.net4.0新增的選擇性參數特性,這樣改:
方法一:使用選擇性參數
public fun1(A a ,B b,C c = 1){ //代碼主體}程式修改完後,在本地程式完美運行,將dll發測試。在測試環境,莫名其妙的報錯。由於系統分層處理,web網站和Web API分離,錯誤返回到最上層,已經看不到錯誤原因,查了老半天日誌,終於發現是另外一個dll(未重新編譯部署)調用了fun1函數,報找不到方法 fun1的錯誤。將程式修改為:
方法二:重載
public fun1(A a ,B b){ fun1(A a ,B b,1);}public fun1(A a ,B b,C c){ //代碼主體}重新發布dll,程式正常運行。
三、初步結論:將代碼修改成選擇性參數,客戶程式需要重新編譯,不然客戶程式調用的時候會報找不到方法的錯誤。如果,客戶程式太多,為了避免部署所有客戶程式的dll,可以用方法二,重載的方法。
四、驗證:方法無預設參數: 方法有選擇性參數: 客戶程式Program編譯後的IL:
五、最終結論: 由圖可知,客戶程式的c#源碼雖然沒有變,但是編譯後的IL中間代碼確改變了,調用的函數也傳了兩個參數,並把函數預設參數888在客戶程式聲明,使用。可以得出的結論是,C#選擇性參數函數其實是在編譯的時候,在函數調用的客戶程式做了處理,把預設參數傳了進去。
六、現在問題來了,大家請思考微軟為什麼要這樣做呢?為什麼不在編譯的時候,IL像以下代碼一樣,做類似重載的處理呢? 這樣的話就不用重新編譯客戶程式了
public fun1(A a ,B b){ fun1(A a ,B b,1);}public fun1(A a ,B b,C c){ //代碼主體}
七、網友解答:感謝CYJB和其他網友的熱心解答,非常詳盡
樓主為方法添加了一個預設參數,那麼這個新的方法和之前的方法的簽名是完全不同的——對應的參數個數是不同的,所以才會出現找不到方法的錯誤。.Net 裡的選擇性參數就像 @衝殺 說的,就是一個文法糖,僅僅是編譯器在方法調用的時候自動加上了預設參數值而已,編譯器完全可能不支援這一功能。
實際上,根據 VS “程式碼分析”功能,可以發現公用方法使用預設參數,是具有一定風險的,因為:在Common Language Specification (CLS) 中允許方法使用預設參數;但是 CLS 允許編譯器忽略為這些參數分配的值。 為忽略預設參數值的編譯器編寫的代碼必須為每個預設參數顯式提供變數。 為了跨程式設計語言維護所需的行為,必須使用提供預設參數的方法重載來替換使用預設參數的方法。完整的文檔可以參考 MSDN CA1026:不應使用預設參數。
最後,公用方法的確是應該只擴充不修改的,以保持良好的相容性,樓主應該是沒有想到 C# 的預設參數只是一個文法糖,而不慎中招了。而微軟為何沒有將預設方法實現為多個重載,不是不願,而是不能。例如下面的方法:
public void TestMethod(int a = 100, int b = 200, int c = 300) {// ...}
如果自動實現為多個重載,顯然是不可能的,因為參數的類型都是 int,無法根據參數類型來區分出參數 a、b 和 c,因此微軟會完全放棄這個方案。而代碼膨脹之類的,可能也在微軟的考慮範圍內。
C#:選擇性參數的陷阱 [轉]