寫到這篇文章的時候,筆者回憶起來之前的開發過程當中,並無注意參數的傳遞是以值傳遞仍是引用傳遞的,也是第一次瞭解到可變參數params,經常使用的不必定就表明理解,可能只是會用。接下來咱們就一塊兒回憶一下關於參數傳遞中得一些方法技巧。數組
可選參數和命名參數性能
在設計方法的參數時,可爲部分或所有參數分配默認值,調用這些方法的代碼,而後調用這些方法的代碼能夠選擇不提供部分實參,使用其默認值,此外,調用方法時可經過指定參數名稱來傳遞實參。下面的例子爲一個有一個形參兩個默認值參數的方法:方法第三次調用S:"張三",就是一個指定參數參數名稱傳參的例子
spa
注意事項:設計
1.可爲方法時,構造器方法和有參屬性的參數指定默認值,還能夠爲屬於委託定義一部分的參數指定默認值,調用該委託的變量時,能夠省略實參使用默認值。code
2.默認值必須是編譯時就肯定的常量值。對象
3.若是參數用ref和out關鍵字進行了標識,就不能設置默認值。blog
4.命名實參只能出如今實參列表的尾部。接口
關於可選參數和命名參數是比較簡單的一部分,可是卻也很實用。內存
隱式類型的局部變量
開發
這種設置參數的方法比較少見,可是多在底層代碼中見到,我回想平時所寫代碼,大部分參數類型都會有所指定,在接下來的文章中咱們會討論參數類型弱化,因此咱們不少時候能夠傳比較基類的參數,好比objeck,T等類型接收參數,可是有的時候咱們但願能夠定義一些比較公共的,以讓方法最大程度複用,C#能根據初始化表達式的類型推斷方法中的局部變量類型
上圖是我寫了個ShowvalueType方法,接收泛型T,不瞭解泛型的能夠百度一下,我用var定義了四種類型的變量,分別是string,int,DateTime,IListI,而後運行結果以下圖,方法準確的識別了每一種數據類型,而且正確的返回了其類型。
以傳引用的方式向方法傳遞參數
這裏是整個參數的重點,也是本文最複雜的難點。ref和out參數從IL代碼上是沒有區別的,但實際上在C#的使用中是存在很大的區別的,咱們仍是沿着CLR中得堆棧概念去分析二者的區別。咱們先來回憶一下值類型和引用類型在堆棧上的存放地址,關於更具體的內容我就不去講了。
可是咱們大致能夠知道,在C#中參數傳遞應該是算有四種狀況的。參數傳遞方式有按值傳遞和按引用傳遞兩種,而C#支持的類型呢,也分爲兩種:值類型和引用類型。因此排列組合一下,就有四種狀況了:值類型按值傳遞、引用類型按值傳遞、值類型按引用傳遞和引用類型按引用傳遞。
值類型參數的按值傳遞
上例代碼就是一個參數傳遞值類型的例子,咱們能夠看出sum的值 並無改變仍是10,咱們知道值類型都是存儲在棧上。那麼實際過程當中發生了什麼呢?實際上的過程是這樣,sum在棧中聲明而且賦值爲10,i也同時在棧中聲明而且賦值爲10,接下來執行++i操做,i的值會變成11,可是sum的值不會跟着改變,仍是10。
引用類型參數按值傳遞:
public sealed class Student { public int Age { get; set; } }
咱們先定義一個Student類,而後在定義一個接收引用類型的方法
1 static void Main(string[] args) 2 { 3 Student stu = new Student { Age = 20 }; 4 Add(stu); 5 Console.WriteLine(stu.Age); 6 7 } 8 public static void Add(Student i) { 9 i.Age++; 10 }
運行結果是21,說明引用類型參數傳遞是內存地址,所以在方法內對變量的任何改變都會影響到原數據。
值類型按引用傳遞:
若是把要把值類型看成引用類型傳遞,必須使用ref或out關鍵字來傳遞變量。示例中使用的是引用類型因此不用ref關鍵字,這個時候咱們回頭在看第一個sum的例子,咱們將其改寫爲,最後的輸出結果就是11,若是使用了ref或者out關鍵字,則調用時,變量名稱前也必須加上ref或out,這就是ref的神奇之處,那麼ref在參數傳遞中到底起了什麼做用呢,咱們能夠理解爲ref 僅僅是一個地址。在文章前面咱們提到過, 每一個變量都有其堆棧,不一樣的變量不能共用一個堆棧地址。因此形參和實參在棧上的地址是不同的。ref所起的做用咱們能夠簡單的理解爲他會記住實參的地址,當形參的值改變後,他會把值映射回 以前的實參地址。
1 static void Main(string[] args) 2 { 3 int sum = 10; 4 Add(ref sum); 5 Console.WriteLine(sum); 6 7 } 8 public static void Add(ref int i) { 9 i++; 10 }
引用類型按引用傳遞:
仍是看剛纔第二個例子,可是這回咱們在改寫一下這個例子,類型不變
public sealed class Student { public int Age { get; set; } }
1 static void Main(string[] args) 2 { 3 Student stu = new Student { Age = 20 }; 4 Add(ref stu); 5 Console.WriteLine(stu.Age); 6 7 } 8 public static void Add(ref Student i) { 9 i.Age++; 10 }
輸出結果是不變的,只是說明了引用類型也能夠這樣傳遞,關於ref和out的卻別
1.ref側重於輸入參數,out側重於輸出參數
2.ref的參數值必須初始化,out的參數能夠沒必要初始化
3.編譯器會按照不一樣標準來驗證代碼是否正確
向方法傳遞可變量的參數
方法有時候須要獲取可變數量的參數,爲了接受可變量,方法要像下面這種形式聲明
1 public static Int32 Add(params int[] values) { 2 int sum = 0; 3 if (values != null) 4 { 5 for (int i = 0; i < values.Length; i++) 6 { 7 sum += values[i]; 8 } 9 } 10 return sum; 11 }
params關鍵字只能應用於方法簽名中的最後一個參數,而且一個方法簽名中只能有一個params關鍵字,那麼爲何params關鍵字聲明,會致使參數變成可變的呢?按照上例代碼,參數應該是一個數組纔對,實際上是C#爲咱們處理了params關鍵字,咱們能夠直接這樣調用
//顯示15 Console.WriteLine(Add(1,2,3,4,5)); //顯示0 Console.WriteLine(Add(null)); //顯示0 Console.WriteLine(Add());
我已經直接把調用結果輸出的值顯示出來了,若是不須要傳參,那麼直接傳null會比傳空更高性能一些。
注意事項:
調用參數可變的方法對性能會有所影響,數組對象必須在堆上分配,數組元素必須初始化,並且數組的內存最終須要垃圾回收,要減小對性能的影響,能夠考慮多定義個不一樣參數的重載版本,減小使用params關鍵字
參數返回類型的設計規範
參數儘量的去使用弱類型而不是強類型,而返回值則儘量的使用強類型不用弱類型,設計規範大體就能夠歸結爲這麼一句話。
聲明方法的類型參數時,應儘可能指定最弱的類型,寧願要接口,也不要基類,例如,若是要寫方法處理一組數據項,最好使用接口(IEnumerable<T>)聲明參數,而不用強類型List<T>或者更強的接口類型。下例就是一個參數,第一個方法能夠處理任何文件流,第二個只能處理Filestream文件流
//好的 public void ProcessBytes(Stream somestram) { } //很差的 public void ProcessBytes(FileStream somestram) { }
而返回值則是越強越好,第一個返回值就告訴你返回的就是一個Filestream文件流,而第二個則返回一個Stream文件流,類型要弱了一些
//好的 public FileStream ProcessBytes() {} //很差的 public Stream ProcessBytes() { }
總結
今天梳理了參數傳遞中的一些狀況,其中參數按值和按引用類型傳遞是重點,在從此的程序設計中更好的設計傳參類型和返回值類型,也是可讓方法變得更好的一種技巧,內容比較多,可能有說明不對的地方,若是有請在評論說明