一、可選參數和命名參數
在設計一個方法的參數時,可為部分或全部參數分配默認值。然后,調用這些方法的代碼時可以選擇不指定部分實參,接受默認值。此外,調用方法時,還可以通過指定參數名稱的方式為其傳遞實參。比如:
internal static class PRogram { private static Int32 s_n = 0; private static void M(Int32 x=9, String s = "A",DateTime dt = default(DateTime), Guid guid = new Guid()) { Console.WriteLine("x={0}, s={1}, dt={2}, guid={3}, x, s, dt, guid"); } public static void Go() { // 1.等同于: M(9, "A", default(DateTime), new Guid()); M(); // 2. 等同于: M(8, "X", default(DateTime), new Guid()); M(8, "X"); // 3. 等同于: M(5, "A", DateTime.Now, Guid.NewGuid()); M(5, guid: Guid.NewGuid(), dt: DateTime.Now); // 4. 等同于: M(0, "1", default(DateTime), new Guid()); M(s_n++, s_n++.ToString()); // 5. 等同于s: String t1 = "2"; Int32 t2 = 3; // M(t2, t1, default(DateTime), new Guid()); M(s: (s_n++).ToString(), x: s_n++); }}在定義的方法中,如果為部分參數指定了默認值,需注意下述原則: 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,為了以傳參數名的方式傳遞實參,請使用下面語法:
// 方法聲明 private static void M(ref Int32 x) { ... } // 方法調用 Int32 a = 5; M(x: ref a); ..... 在C#中,一旦為某個參數分配了一個默認值,編譯器就會在內部像該參數應用一個定制attibute,即System.Runtime.InteropServices.OptionalAttribute。這個attribute會在最終生成的文件的元數據中持久性地存儲下來。此外,編譯器還會向參數引用一個名為System.Runtime.InteropServices.DefaultParameterValueAttribute的attribute,并將這個attribute持久性存儲在最終文件的元數據中,然后,會向DefaultParameterValueAttribute的構造器中傳遞你在源代碼中指定的常量值。之后,一旦編譯器發現一個方法調用缺失了部分實參,就可以確定省略的是可選的實參,并從元數據中提取它們的默認值,將這些值自動嵌入調用中。 之后,一旦編譯器發現一個方法調用缺失了部分實參,就可以確定省略的是可選的實參,并從元數據中提取它們的默認值,并將這些值自動嵌入調用中。二、隱式類型的局部變量
針對一個方法中的隱式類型的局部變量,C#允許根據初始化表達式的類型來判斷它的類型。
private static void ImplicitlyTypedLocalVariables() { var name = "Jeff"; ShowVariableType(name); // 類型是: System.String // var n = null; // 錯誤 var x = (Exception)null; // 可以這樣寫,但沒意義 ShowVariableType(x); // 類型是: System.Exception var numbers = new Int32[] { 1, 2, 3, 4 }; ShowVariableType(numbers); // 類型是: System.Int32[] // 針對復雜類型,可減少打字量 var collection = new Dictionary<String, Single>() { { ".NET", 4.0f } }; // 類型是: System.Collections.Generic.Dictionary`2[System.String,System.Single] ShowVariableType(collection); foreach (var item in collection) { // 類型是: System.Collections.Generic.KeyValuePair`2[System.String,System.Single] ShowVariableType(item); } } 隱式類型的局部變量是局部變量,不能用它聲明方法的參數。也不能聲明一個類型的字段。 用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才有意義。四、向方法傳遞可變數量的參數 有的時候,開發人員想定義一個方法來獲取可變數量的參數。為了聲明方法接受可變數量的參數,如下: private static Int32 Add(params Int32[] values) { Int32 sum = 0; for (Int32 x = 0; x < values.Length; x++) sum += values[x]; return sum; }params關鍵字只能應用于方法參數列表的最后一個參數。
我們調用時可以這樣: //顯示 "15" Console.WriteLine(Add(new Int32[] { 1, 2, 3, 4, 5 }));也可以這樣:
// 顯示 "15" Console.WriteLine(Add(1, 2, 3, 4, 5));由于params關鍵字的存在,所以可以這么做。params關鍵字告訴編譯器向參數引用System.ParamArrayAttribute的一個實例。 只有方法的最后一個參數才能用params關鍵字(ParamArrayAttribute)來標記。另外,這個參數只能標識任意類型的一個一位數組。可為這個參數傳遞null值,或傳遞對包含另個元素的一個數組的引用。
// 顯示"0" Console.WriteLine(Add()); Console.WriteLine(Add(null));那么如果寫一個方法來獲取任意數量、任意類型的參數呢?只需要修改方法原型,讓它獲取一個Object[]而不是Int32[]。比如
private static void DisplayTypes(params Object[] objects) { foreach (Object o in objects) Console.WriteLine(o.GetType()); }五、參數和返回類型的指導原則
1)聲明方法的參數類型時,應盡量指定最弱的類型,最好是接口而不是基類。 例如,如果要寫一個方法處理一組數據項,最好是用接口(比如IEnumerable<T>)來聲明方法的參數,而不要使用強數據類型(比如List<T>)或者更強的接口類型(比如ICollection<T>或IList<T>): //好 public void MainpulateItems<T>(IEnumerable<T> collection) { ... } //不好 public void MainpulateItems<T>(List<T> collection) { ... } //好:該方法使用弱參數類型 public void ProcessBytes(Stream someStream) { ... } //不好:該方法使用強參數類型 public void ProcessBytes(FileStream someStream) { ... }2)一般最好將方法的返回類型聲明為最強的類型,以免受限于特定類型。例如:
//好:該方法使用強返回值類型 public FileStream ProcessBytes() { ... } //不好:該方法使用弱返回值類型 public Stream ProcessBytes() { ... } 第一個方法是首選的,它允許方法的調用者選擇將返回對象視為一個FileStream對象或者一個Stream對象。但是,第二個方法要求調用者將返回對象視為一個Stream對象。總之,確保調用者在調用方法時有盡量大的靈活性,使方法的應用范圍更大。六、常量性
CLR沒有提供對常量參數/對象的支持。新聞熱點
疑難解答