主要探討C# Function參數的使用方式與意義,希望對想要更了解C#本質的朋友有些許幫助,如有說明錯誤也請不吝指教。
首先探討這個主題之前。我想先解釋一些一點指標的概念。
在C#中,幾乎把指標特性給完全隱藏,就像"好自在"一樣讓你在寫C#時,幾乎感覺不到它的存在。
因此C#很適合新手入門,但要完全駕馭FUNCTION參數這個觀念,不懂指標是非常難以理解的。
指標顧名思義就是指定的目標,但何物所指,又指向何物(似乎有點饒口XD),就是我們所要注意的了!
C#在建立一個變數時,系統會去調用記憶體分配記憶體位置給這個變數,以便儲存這個變數的值。
保存這個記憶體位址的變數,我們稱為指標變數。
在C 語法中,我們可以寫成
int i=0;
int *point=&i;
語法意義這邊不詳述,point 就是一個指標變數。
大家一定覺得納悶,我C#寫得好好的管C幹嘛,讓我細細道來~
舉兩個例子給大家思考一下
EX.1
private static void Main(string[] args)
{
//Integer
int i = 0;
changeInteger(i);
Console.WriteLine(i);
}
public static void changeInteger(int i)
{
i = 10;
}
這個例子,想透過changeInteger的function去改變i的值,結果輸出還是為0,表示這個function沒辦法改動i。(X!)
EX.2
private static void Main(string[] args)
{
Employee emp = new Employee()
{
name="charles",
salary = 100
};
changeSalary(emp);
Console.WriteLine(emp.salary);
}
private static void changeSalary(Employee employee)
{
employee.salary = 999;
}
Employee是我自己定義的Class Name,裡面有Name與Salary兩個屬性。我透過changeSalary去改變它的salary的值。
結果輸出為999,太神奇拉~BUT WHY?為啥這個function就可以改變傳入的參數的值?????????
這是因為本質上,C#的物件處理與Integer處理上有很大的不同,必須了解資料型別的差異。
C#中資料型別可分為,Value Type & Reference Type,可詳閱這篇MSDN文章。By the way,String是Reference Type,想知道為什麼需要C語言的基礎知識,這裡就不多談了。
簡單來說,C# Value Type變數是傳 "值" 的方式來傳遞,而Reference Type變數是傳 "位址" 的方式來傳遞。
這在function調用上,會造成很大的差異。
在EX1,int 屬於 Value Type,所以在changeInteger(int i)內,系統會重新配置記憶體給function的參數i,所以在該function中去改變i的值,並不會影響外面傳入的參數i。
在EX2,自定義的 Class Object是屬於Reference Type,因此會傳遞emp的地址,也就是先前所提的指標變數,給changeSalary(Employee employee)這個function,傳給employee,所以在該function中去改變employee其Salary的值,則就改變了外面傳入的物件emp Salary的值。結合C語言,其實可以把emp視為一個指標變數,而並非直觀上我們所想的一整個物件。
前面用兩個例子,來解釋call by value 與 call by reference的差異。
接者我將要來跟大家說明ref & out參數的特性。
Ref 參數
Ex3.
private static void Main(string[] args)
{
//Integer
int i = 0;
changeInteger(ref i);
Console.WriteLine(i);
}
public static void changeInteger(ref int i)
{
i = 10;
}
輸出結果變成10了!意味著現在參數i加上ref修飾字後,可以在changeInteger function內,改變外部i的值了。
根據前面的理解,我們可以推測出,加上ref後,其實是將該變數的指標,傳遞給function。因次可以改變外部的值。
這是Value Type的部分,
接著我們繼續探討,Reference Type前面有ref時,會怎麼呈現。
EX4.
private static void Main(string[] args)
{
Employee emp = new Employee()
{
name = "charles",
salary = 100
};
changeSalary(emp);
//此時emp 已經是changeSalary function內所產生的新物件的位址
//已經不是Main一開始所以新建物件的地址。所以輸出為999。
Console.WriteLine(emp.salary);
}
private static void changeSalary(ref Employee employee)
{
//傳入指標的位址 被改寫成新物件的指標
employee = new Employee() {salary = 999};
}
EX4內,假設 changeSalary 是直接改值寫成 employee.salary=999,則是表示直接改了外部物件的值,所以在這個case 有沒有加ref行為是沒有差異的。
但如果是將傳入的參數,指派新的物件,意義上就大大的不同了。系望大家可以瞭解這一點,在外表操作的物件是兩個不一樣的東西。
再考大家一下,要是我將ref拿掉,function寫成以下
private static void changeSalary(Employee employee)
{
employee = new Employee() {salary = 999};
}
結果又會是如何?答案:100,原因給大家去思考,想信前面如果了解這個應該是可以理解的。
再來是最後一個修飾字OUT了。
先看個例子
EX5.
private static void Main(string[] args)
{
Employee emp ;
changeSalary(out emp);
Console.WriteLine(emp.salary);
}
private static void changeSalaryOut(out Employee employee)
{
//Console.WriteLine(employee.salary);//該行會出現編譯錯誤,
//因為employee未被初始化,單純作為一個輸出的物件給呼叫端去使用
employee = new Employee() {salary = 999};
//只要employee 沒被初始化過 整個method將會編譯錯誤
}
Out 使用非常方便,傳入參數給function與外部有沒有初始化都沒關係,因為最後回傳是function裡面產生物件的指標。只要在function內沒有初始化,就想操作參數編譯都是會出錯的。
而ref一定要初始化,因為沒初始化,系統還沒分配位址給變數。
到這邊也闡述所有修飾字差不多了。想徹底明白,指標的觀念非常重要,可以幫助理解為啥C#會有這樣的行為。
是不是很有趣呢?