【C#進階系列】09 關於參數的故事


可選參數和命名參數

不多說,上代碼,自然懂

  class Program
{
static void Main(string[] args)
{
var troy = new Troy();
troy.HelloWorld(
1);//此時b和c都為0
troy.HelloWorld(1,2);//此時b為2,c為0,以上兩個為可選參數的玩法

troy.HelloWorld(a:
1, b: 2);//命名參數玩法
troy.HelloWorld(b: 2, a: 1);//即使順序打亂,效果也是一樣
}
}
public class Troy {
public void HelloWorld(int a, int b = 0,int c=default(int)) {//這里b和c參數就是可選參數
//注意default(int)這種玩法,表示int的默認值。
//我是第一次知道這種用法,然而非常推崇這樣的玩法,因為可以有效減少你代碼中的魔法數字。也許你認為0這種不算魔法數字,然而我認為能讓代碼更簡單易懂一點點也是非常有必要的。
//就算不用魔法數字,那么default(DateTime)去判斷DateTime值是否為默認值,是不是比new Datetime()更好一點呢?
}
}

默認參數實際上在C#編譯器編譯過后就向該參數應用特性OptionalAttribute和DefaultParameterValueAttribute。並不是CLR支持的,而是C#特有的。

這兩個東西看上去都那么美好,然而美好的東西並不一定真的好。

推薦用命令參數,然而不要為了調換順序而調換順序。實際上對VS這么強大的工具而言,命名參數只有在參數非常多的時候才有用。

如果你的參數太多,那么其實更應該考慮縮小一下參數的數量。可以考慮提取一個參數對象,傳值的時候傳這個參數對象就好了。

雖然我非常喜歡用默認參數,實際上它也確實很好用,特別對於重載而言,你有的時候根本沒必要去寫兩個函數。

然而這實際上也是個坑點。

如果你和我一樣喜歡用,你團隊的人也喜歡用。那么最后你會發現,這個東西總是會讓函數內部充滿了各種各樣的分支,你的參數列表也會越來越長。o(︶︿︶)o 唉

如果你不懂那么可以看一下我寫的一個代碼維護小故事,請想象一下你在維護的是一個業務復雜的大型系統,那么接下來的節奏會經常發生。

     //最開始Troy寫了一個打招呼的函數
public void 打招呼()
{
Console.WriteLine(
"你好");
//下面還有一系列握手,微笑之類的操作
}
//后來老板說國際化,也要能英文打招呼.你選擇了默認參數,並且為了保證以前的代碼順利運行,默認為false
public void 打招呼(bool IsEnglish=false)
{
if (IsEnglish)
{
Console.WriteLine(
"Hello");
}
else {
Console.WriteLine(
"你好");
}
//下面還有一系列握手,微笑之類的操作
}
//到了上面那一步也是OK的,然而過了兩天,老板跟大牛說有的老外有可能不握手,他選擇擁抱。於是大牛改代碼:
public void 打招呼(bool IsEnglish = false, bool 是否選擇擁抱 = false)
{
if (IsEnglish)
{
Console.WriteLine(
"Hello");
}
else {
Console.WriteLine(
"你好");
}
if (是否選擇擁抱)
{
Console.WriteLine(
"擁抱");
}
else {
Console.WriteLine(
"握手");
}
//下面還有一系列微笑之類的操作
}

當然即使到現在,以上的代碼看起來也僅僅只是分支增多而已,然而請你設身處地去想一下,如果這個系統的業務很復雜,如果后面還有需求,如果不僅僅只是一個Console.WriteLine這么簡單的操作。

等到第四次去修改的時候,新來的項目組成員小菜已經沒得選了。首先他不熟悉業務,不敢亂改,他甚至可能熟悉也懶得改,因為改起來已經很麻煩了(不要太相信你的隊友,我就是這樣的懶人(☆_☆)),那么這個時候他只能默默選擇再加一個默認參數。

有第四個,肯定就會有第五個,每次想要重構感覺難度越來越大,心里越來越虛,只好隨大流去加默認參數,經過大家一起努力,這段代碼已經差不多10個分支了,大家都不敢改了。

如果從一開始不選擇加默認參數,而是多寫一個重載函數,將公共部分提煉出來,那么你覺得還會有這樣的問題嗎?

然而並沒有什么鳥用,即使是我了解的這么清楚,我經常會覺得我在加第三次默認參數完全沒有任何問題,偷點懶趕緊下班啦,代碼依然能看,等我下次回來改的時候,我發現默認參數已經變成5個了。o(︶︿︶)o 唉

out和ref的故事

CLR不區分out和ref,生成的IL代碼一模一樣,只是元數據會有個bit值加以區分。

out表示傳遞的是引用類型,然而他並不需要調用的時候這個參數就初始化完畢,且要求函數執行完畢的時候out參數必須被寫入過。

ref要求調用的時候這個參數就初始化完畢,且不要求函數執行完畢的時候ref參數必須被寫入過。

實際上在C里面這個東西就是指針,我們這里來講這東西傳遞的就是值的地址。

如果有大的值類型的傳參,比如一個大型struct。

那么用這種方法傳參只會傳一個地址值,而不是把每個struct里面的值都壓到棧中。

另外不要因為ref比out好用就不用out,out可以明確你這個參數一定會傳值出來,讀代碼更容易。

pramas傳遞可變數量的參數

這個網上一大堆,我用得最多的就是String.Format方法,可以參考這個來了解。

不過要注意這個會有性能損失,因為傳遞的pramas一維數組,實際上是分配在堆空間中的,初始化啊,垃圾回收啊,都會有影響。

參數和返回類型的設計規范

聲明方法的參數類型時,應該按照最低接口類型,更強的接口類型,基類,派生類從高到低的優先級來聲明類型。

因為用接口或者基類會讓你的方法更加靈活,可以選擇的余地更大。

而返回類型正好相反,防止受限於特定類型。

以上是作者講的,有道理,但是具體情況具體分析,難道要我們都去聲明object,那一切都OK了?

作者的意思顯然不是如此。我認為你可以在第一次的時候去選擇最適合的強類型參數,但是下一次有一個類似的函數時,而兩個參數間有一個共同的接口或者基類,那么是否可以考慮一下將參數的類型弱化呢?這樣明顯可以讓代碼更靈活。

但是不要因此直接就用個object之類的,不要過度設計,永遠用目前最滿足需求的那個,不要把未來全部考慮完全,因為你根本預測不到未來。

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com