可選參數和命名參數 不多說,上代碼,自然懂 class Program { static void Main(string[] args) { var troy = new Troy(); troy.HelloWorld(1);//此時b和c都為0 troy.HelloWorld(1,2);//此時
可選參數和命名參數
不多說,上代碼,自然懂
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之類的,不要過度設計,永遠用目前最滿足需求的那個,不要把未來全部考慮完全,因為你根本預測不到未來。