第 117 講:C# 4 之命名和可選參數(shù)
今天我們要說的是命名參數(shù)和可選參數(shù)。這個特性是為了更加輕便、靈活使用參數(shù)而制定的語法。先來說可選參數(shù)。
Part 1 可選參數(shù)
可選參數(shù)(Optional Parameter)是一種將兩個重載版本代碼基本一致的時候,抽取成一個方法的操作。
1-1 基本語法
比如說,我現(xiàn)在要使用冒泡排序法來排序,不過我們?yōu)榱俗層脩艨梢造`活使用,我們再提供一個重載版本,讓用戶自定義排序的委托。
if
我們注意到第 7 到第 9 行代碼,我們使用了一個三目運算符來判斷這個參數(shù)。如果這個參數(shù)是 null
,就說明它是初始值,于是就使用 int
的默認比較規(guī)則來進行比較;否則,如果定義了的話,就使用 comparison
參數(shù)來進行比較。
注意,由于只有常量是安全使用,所以只能往參數(shù)上賦常量數(shù)值。如果你要賦變量或者是別的成員引用的話,是不允許的,畢竟你沒辦法確定這樣做是否會變更對象的信息,導(dǎo)致不安全的事情發(fā)生。
我們把 comparison
參數(shù)稱為可選參數(shù)。它上面有一個默認數(shù)值。那么這個方法怎么使用和調(diào)用呢?
由于該參數(shù)具有默認數(shù)值,因此如果你不使用參數(shù)的話,可以完全不考慮此參數(shù),直接當成“少一個參數(shù)”的方法來進行調(diào)用。比如說,這個參數(shù)有兩個參數(shù),第一個參數(shù)必須傳入,那么我們傳入 arr
變量;而第二個參數(shù)保持 null
就不管它:
就可以了。它和寫成 Sort(arr, null);
是一樣的。
1-2 注意事項
除了前面說的只能賦常量作為默認數(shù)值這個限制以外,還具有一些注意事項。
第一個,它并不會影響你代碼的執(zhí)行和簽名規(guī)則。它只是給參數(shù)帶了一個初始數(shù)值,但是參數(shù)的個數(shù),參數(shù)的類型等等信息,都是不受影響的,所以比如重載兩個方法,如果僅僅區(qū)別在于可選參數(shù)上的話(比如有一個重載有賦默認參數(shù)數(shù)值,有一個沒有),這樣并不會構(gòu)成重載。
第二個,它的有無不僅僅是可以用于實際方法上,也可以用于抽象的成員上。這里的抽象除了指抽象類型(包含 abstract
修飾符的方法),還可以用戶接口的方法成員。舉個例子:
如代碼所示,C
類型同時實現(xiàn)了兩個接口 I
和 J
,且這兩個接口里的方法 F
唯一區(qū)別只有參數(shù)是可選和不可選而已。但是,在 C
類型的實現(xiàn)上,是不需要你關(guān)心是否也需要帶默認參數(shù)數(shù)值的。在第一點里我沒說過,它不影響程序的編譯和重載規(guī)則,因此 I
和 J
即使有可選參數(shù)的區(qū)別,但它們倆不構(gòu)成邏輯上的重載。換句話說,哪怕這倆方法寫在同一個接口里,編譯器是會報錯的,畢竟會被認為是同一個方法:同一個方法不能重復(fù)聲明。
但是,在實現(xiàn)上,C
類型不必關(guān)心該參數(shù)是否是可選的。這是為什么呢?因為該參數(shù)是否可選取決于你自己的實現(xiàn),所以你需要就設(shè)定,不需要就不設(shè)定,至少編譯器和運行時都知道你下一步的調(diào)用行為。我們使用多態(tài)規(guī)則來試試吧:
我們實例化了兩個 C
類型的對象,然后分別賦值給 I
和 J
接口的實例。因為 C
類型實現(xiàn)了這倆接口,所以這種轉(zhuǎn)換是成功的。接著,我們對 i
和 j
變量都調(diào)用各自里面包含的 F
方法。由于 I
接口里包含的 F
方法具有可選參數(shù),因此你可以對此使用這樣的特性,不給該參數(shù)賦值,這樣的話,參數(shù)默認數(shù)值就是 42;而 J
接口的方法參數(shù)不是可選參數(shù),因此必須傳入數(shù)值進去。
這樣程序也不會有問題,而且也是預(yù)期的。
第三個,不僅參數(shù)的默認數(shù)值只能設(shè)置為常量,而且還不能有隱式轉(zhuǎn)換。舉個例子。
這種奇怪的賦值是合理的嗎?答案是否定的。這種賦值是錯誤的。雖然我們知道,字符串可以往 IEnumerable<char>
賦值,畢竟 string
實現(xiàn)了這個接口類型,但在可選參數(shù)上,這樣的賦值是不被允許的,原因在于它具有一次隱式轉(zhuǎn)換。而隱式轉(zhuǎn)換是運行時行為,換句話說,這種操作是不可在編譯時期而推斷的行為,畢竟你不知道后果如何。當然了,這里的轉(zhuǎn)換我們可以猜想到可能是沒有毛病的,但是對于一些我們自定義的類型來說,假設(shè)它們實現(xiàn)了這樣的接口,然后你去將字符串賦值過去。假設(shè)這個語法允許,那么它就會強制在編譯時期得到一次隱式轉(zhuǎn)換的規(guī)則,但你可以在這個隱式轉(zhuǎn)換的過程植入很多不是預(yù)期的代碼,然后逃避編譯器的檢查,這樣是很危險的。所以,這種賦值過程不被允許。
第四個,可選參數(shù)只能放在參數(shù)表列的末尾。舉個例子,比如你這么寫代碼是不可以的:
假設(shè)這樣的語法成立,我們調(diào)用該方法的時候,如果不對可選參數(shù)賦值,那么方法就成了這樣:
因為第一個參數(shù)我們不需要傳入數(shù)值,而你又必須標記一個逗號來分隔參數(shù),暗示 42 是第二個參數(shù)的位置上的數(shù)值。這樣肯定是不合語法邏輯和規(guī)則的。因此,C# 不允許這樣的語法。不過,類似這種缺省的調(diào)用語法,在別的編程語言可能是有的,比如 Visual Basic 里是支持這種寫法的。
第五個,可選參數(shù)不一定只能有一個??蛇x參數(shù)只要放在參數(shù)表列的末尾,你想有多少個都行,你甚至全都是可選參數(shù)也行。
Part 2 命名參數(shù)
命名參數(shù)(Named Parameter)的語法是,允許用戶將參數(shù)名寫在調(diào)用方。
2-1 基本語法
舉個例子,還是使用冒泡排序:
比如在第 4 行,我們傳入一個 arr
的時候,還允許我們手動寫 array:
來表示這個參數(shù)。嗯,說完了。
2-2 這有什么用?
可能有很多小伙伴覺得,這能有什么用?我為啥要花時間去多寫一下參數(shù)名稱?它的存在是有兩點意義:
標注一些常量參數(shù)傳入的時候的參數(shù)名,這樣使得代碼更具可讀性;
約束參數(shù)調(diào)用,使得我們的可選參數(shù)和必需參數(shù)(Required Parameter)使用起來更靈活。
我們來說說這兩點。
第一。假如我們傳入的參數(shù)是一個常量或字面量數(shù)值,這個時候如果沒有標注的話,就會顯得很難去看,特別是參數(shù)特別多的時候:
假設(shè)我有一個帶 6 個參數(shù)的方法。調(diào)用的時候假設(shè)長這樣:
對是對,但是我咋知道這些參數(shù)是啥。難不成我挨個都要去把鼠標放上去看?這不是遭罪么。那么我們有了命名參數(shù)之后,參數(shù)名寫出來就顯得很方便了:
雖然代碼更長了,但是起碼做到了參數(shù)名確定起來,代碼可讀性提高的作用。它也不影響你程序運行,寫不寫都對,但寫了更好看,所以命名參數(shù)有這個用。
第二。我們再來看這個方法。假如我們對最后兩個參數(shù)搞成可選參數(shù):
假如我指向給 maximumValue
參數(shù)賦一個另外的數(shù)值,但 minimumValue
我還是想用這個 0,這能做到嗎?
能。有了命名參數(shù)就能。我們只需要給前四個參數(shù)賦值之后,帶上參數(shù)名即可:
這樣寫之后,編譯器就知道,前面四個參數(shù)數(shù)值對位賦值到前面四個方法的參數(shù)里去,而最后一個參數(shù)由于給了參數(shù)名,因此會被識別為最后一個參數(shù) maximumValue
,于是賦值給這個參數(shù)上。如果你不寫的話,這個時候按照賦值的匹配規(guī)則,順次賦值到方法參數(shù)之中去,就會讓 minimumValue
參數(shù)賦上 60 的數(shù)值,這就不是我們想要的結(jié)果了。因此,命名參數(shù)還有一個強約束可選參數(shù)的賦值的功能。
這就是這兩種參數(shù)的用法。我們就說完了。C# 4 也就全部結(jié)束了。
接下來是 C# 5。C# 5 的特性也不多(數(shù)下來一共也就三個特性),但是會有開幕雷擊的效果,因為第一個特性“異步方法”是特別難的點。在這個特性里我們會接觸到多線程的新架構(gòu)和模型,這也是我在早期講解多線程的時候挖的坑。在這里我們就會填坑完善這個多線程的使用體系和規(guī)則;另外,它還帶有一個全新的語法:async
和 await
關(guān)鍵字。如果用不好的話,會讓人完全不知道這個語法設(shè)計起來到底應(yīng)該怎么用。所以這個特性特別有得說。
而我打算停更一段時間,因為確實有點累,每四天更新一次這個語法教程的話,前面的語法還好,特別是這個 C# 5 的這個語法,有點復(fù)雜,四天說不定都不一定趕得上,而且感覺腱鞘炎有些嚴重了,所以我想暫時先休息一陣子。