小知識:C#可選參數的一個陷阱


一、背景:
互聯網行業,為了降低程序維護、升級的部署風險,往往會將程序拆分成很多項目,編譯成多個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)
{
   //代碼主體
}

 

七、網友解答:感謝34樓CYJB和其他網友的熱心解答,非常詳盡

樓主為方法添加了一個默認參數,那么這個新的方法和之前的方法的簽名是完全不同的——對應的參數個數是不同的,所以才會出現找不到方法的錯誤。.Net 里的可選參數就像 @沖殺 說的,就是一個語法糖,僅僅是編譯器在方法調用的時候自動加上了默認參數值而已,編譯器完全可能不支持這一功能。

實際上,根據 VS “代碼分析”功能,可以發現公共方法使用默認參數,是具有一定風險的,因為:在公共語言規范 (CLS) 中允許方法使用默認參數;但是 CLS 允許編譯器忽略為這些參數分配的值。 為忽略默認參數值的編譯器編寫的代碼必須為每個默認參數顯式提供變量。 為了跨編程語言維護所需的行為,必須使用提供默認參數的方法重載來替換使用默認參數的方法。完整的文檔可以參考 MSDN CA1026:不應使用默認參數

最后,公共方法的確是應該只擴展不修改的,以保持良好的兼容性,樓主應該是沒有想到 C# 的默認參數只是一個語法糖,而不慎中招了。而微軟為何沒有將默認方法實現為多個重載,不是不願,而是不能。例如下面的方法:

public void TestMethod(int a = 100, int b = 200, int c = 300) 
{
// ...
}

 

 


如果自動實現為多個重載,顯然是不可能的,因為參數的類型都是 int,無法根據參數類型來區分出參數 a、b 和 c,因此微軟會完全放棄這個方案。而代碼膨脹之類的,可能也在微軟的考慮范圍內。

 


免責聲明!

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



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