一、可選參數和命名參數
在設計一個方法的參數時,可為部分或全部參數配置設定預設值。然後,調用這些方法的代碼時可以選擇不指定部分實參,接受預設值。此外,調用方法時,還可以通過指定參數名稱的方式為其傳遞實參。比如:
在定義的方法中,如果為部分參數指定了預設值,需注意下述原則:
1)可以為方法、構造器方法和有參屬性(C#索引器)的參數指定預設值。還可為屬于委托定義一部分的參數指定預設值。然後,在調用該委托類型的一個變量時,可以省略實參,以接受預設值。
2)有預設值的參數必須放在沒有預設值的所有參數之後。換言之,一旦定義了一個有預設值的參數,它右邊的所有參數也必須有預設值。但有個例外:"參數數組"這種參數必須放在所有參數(包括有預設值的這些)之後,而且數組本身不能有一個預設值。
3)預設值必須是編譯時能确定的常量值。這些參數的類型可以是C#認定的基元類型,還包括枚舉類型,以及設為null的任何引用類型。對于任何值類型的一個參數,可将預設值設為值類型的一個執行個體,并讓它的所有字段都包含零值。可以用default關鍵字或者new關鍵字來表達這個意思。如在M方法中設定dt參數和guid參數的預設值,就是用的這兩種文法。
4)注意不要重新命名(即修改)參數變量名稱。否則,任何調用者如果以傳參數名的方式傳遞實參,都必須修改它們的代碼。
5)如果方法是從子產品的外部調用的,更改參數的預設值具有潛在的危險性。調用方會在它的調用中嵌入預設值。如果以後更改參數的預設值,但沒有重新編譯調用方所在的代碼,它在調用你的方法時就會傳遞就得預設值。可考慮将預設值設為0/null作為哨兵值(起到占位子作用)使用。
6)如果參數使用ref或out關鍵字進行了辨別,就不能設定預設值。因為沒有辦法為這些參數傳遞一個有意義的預設值。
使用可選或命名參數調用一個方法時,還要注意下述原則:
1)實參可按任何順序傳遞;但是,命名實參隻能出現在實參清單的尾部。
2)可按名稱将實參傳給沒有預設值的參數。
3)C#不允許省略都好之間的實參,比如M(1, ,DateTime.Now)。
4)如果參數需要ref/out,為了以傳參數名的方式傳遞實參,請使用下面文法:
在C#中,一旦為某個參數配置設定了一個預設值,編譯器就會在内部像該參數應用一個定制attibute,即System.Runtime.InteropServices.OptionalAttribute。這個attribute會在最終生成的檔案的中繼資料中持久性地存儲下來。此外,編譯器還會向參數引用一個名為System.Runtime.InteropServices.DefaultParameterValueAttribute的attribute,并将這個attribute持久性存儲在最終檔案的中繼資料中,然後,會向DefaultParameterValueAttribute的構造器中傳遞你在源代碼中指定的常量值。之後,一旦編譯器發現一個方法調用缺失了部分實參,就可以确定省略的是可選的實參,并從中繼資料中提取它們的預設值,将這些值自動嵌入調用中。
之後,一旦編譯器發現一個方法調用缺失了部分實參,就可以确定省略的是可選的實參,并從中繼資料中提取它們的預設值,并将這些值自動嵌入調用中。
二、隐式類型的局部變量
針對一個方法中的隐式類型的局部變量,C#允許根據初始化表達式的類型來判斷它的類型。
隐式類型的局部變量是局部變量,不能用它聲明方法的參數。也不能聲明一個類型的字段。
用var聲明的局部變量隻是一種簡化文法,它要求編譯器根據一個表達式推斷具體的資料類型。var關鍵字隻能用于聲明方法内部的局部變量,而dynamic關鍵字可用于局部變量,字段和參數。表達式不能轉型為var,但可以轉型為dynamic。必須實作初始化化var聲明的變量,但無需初始化用dynamic聲明的變量。
三、以傳遞引用的方式向方法傳遞參數
預設情況下,CLR假定所有的方法參數都是傳值的。
傳遞引用類型的對象時,對一個對象的引用(或者說指向對象的指針)會傳給方法。但這個引用(或指針)本身是以傳值方式傳給方法的。這意味着方法能修改對象,而調用者能看到這些修改。對于值類型的執行個體,傳給方法的是執行個體的一個副本,這意味着方法将擷取它專用的一個值類型執行個體副本,調用中的執行個體不受影響。
CLR中允許以傳引用而非傳值的方式傳遞參數。在C#中,這是用關鍵字out和ref。這兩個關鍵字都告訴C#編譯器生成的中繼資料來指明該參數時傳引用的。編譯器将生成代碼來傳遞參數的位址,而不是傳遞參數本身。
從CLR角度看,關鍵字out和ref完全一緻。這就是說,無論用哪個關鍵字,都會生成相同的IL代碼。另外,中繼資料也幾乎一緻。隻有一個bit除外,它用于記錄聲明方法時指定的是out還是ref。
C#編譯器是将者兩個關鍵字差別對待的,而且這個差別決定了有哪個方法負責初始化所引用的對象。
如果方法的參數用out來标記,表明不指望調用者在調用方法之前初始化好了對象。被調用的方法不能讀取參數的值,而且在傳回前必須向這個值寫入。相反,如果方法的參數用ref來标記,調用者就必須在調用方法前初始化參數的值,被調用的方法可以讀取值或者寫入值。
為值類型使用out和ref,效果等同于以傳值的方式傳遞引用類型。對于值類型,out和ref允許方法操縱單一的值類型執行個體。調用者必須為執行個體配置設定記憶體,被調用者則操縱該記憶體中的内容。
對于引用類型,調用代碼為一個指針配置設定記憶體(該指針指向一個引用類型的對象),被調用者則操縱這個指針。正因為如此,僅當方法"傳回"對"方法知道的一個對象"的引用時,為引用類型提供out和ref才有意義。
四、向方法傳遞可變數量的參數
有的時候,開發人員想定義一個方法來擷取可變數量的參數。為了聲明方法接受可變數量的參數,如下:
params關鍵字隻能應用于方法參數清單的最後一個參數。
我們調用時可以這樣:
也可以這樣:
由于params關鍵字的存在,是以可以這麼做。params關鍵字告訴編譯器向參數引用System.ParamArrayAttribute的一個執行個體。
隻有方法的最後一個參數才能用params關鍵字(ParamArrayAttribute)來标記。另外,這個參數隻能辨別任意類型的一個一位數組。可為這個參數傳遞null值,或傳遞對包含另個元素的一個數組的引用。
那麼如果寫一個方法來擷取任意數量、任意類型的參數呢?隻需要修改方法原型,讓它擷取一個Object[]而不是Int32[]。比如
五、參數和傳回類型的指導原則
1)聲明方法的參數類型時,應盡量指定最弱的類型,最好是接口而不是基類。
例如,如果要寫一個方法處理一組資料項,最好是用接口(比如IEnumerable<T>)來聲明方法的參數,而不要使用強資料類型(比如List<T>)或者更強的接口類型(比如ICollection<T>或IList<T>):
2)一般最好将方法的傳回類型聲明為最強的類型,以免受限于特定類型。例如:
第一個方法是首選的,它允許方法的調用者選擇将傳回對象視為一個FileStream對象或者一個Stream對象。但是,第二個方法要求調用者将傳回對象視為一個Stream對象。總之,確定調用者在調用方法時有盡量大的靈活性,使方法的應用範圍更大。
六、常量性
CLR沒有提供對常量參數/對象的支援。