IT系統集成商

系統集成 | 虛擬化應用 | 群暉網絡存儲 | 視頻會議 | 數據防泄密 | 技術運維

手機: 130-1534-6247   電話: 0351-2396570

C#函數參數傳遞很容易混淆的概念

C#中的數據類型分為值類型引用類型兩大類。
 
值類型:int,float,bool,char,enum,struct都是值類型。
 
引用類型:string,數組,class,接口,委托。其中的string是比較特殊的引用類型。C#給它增加個字符恒定的特性。
 
值類型:直接存儲數據的值,保存在內存中的stack(堆棧)。
 
引用類型:存儲對值的引用,實際上存儲的就是一個內存的地址。引用類型的保存分成兩塊,實際值保存在托管堆(heap)中。實際值的內存地址保存在stack中
 
C#函數的參數如果不加ref,out這樣的修飾符顯式申明參數是通過引用傳遞外,默認都是值傳遞。
 
這里要注意的一個問題是,參數的類型是值類型還是引用類型和傳參數時用值傳遞還是引用傳遞是兩個不同的概念。
 
假如有void ChangeArray(int [] array) 和void ChangeInt(int a) 這兩個函數。參數array是引用類型,a是值類型。但是他們傳遞時都是按值傳遞。
 
我們來舉個例子說明下

按值傳遞參數:

class Program
    {
        public static void ChangeInt(int num)
        {
            num = 100;
        }
 
        public static void ChangeArray(int[] arraynew)
        {
            arraynew[0] = 10;
            arraynew= new int[] { 6, 7, 8, 9 };
        }
 
        static void Main(string[] args)
        {
            int anum = 1;
            int[] array = { 1, 2, 3 };
            ChangeInt(anum);
            ChangeArray(array);
            Console.WriteLine("value of num: " + anum);
            Console.Write("value of array: ");
            foreach (int i in array)
                Console.Write(i + " ");
        }
 
    }
 
結果是:value of anum : 1
 
       value of array :10 2 3
 
可能看到結果會有點奇怪。我們一般認為值傳遞就是把值拷貝一份,然后不管在函數中對傳入的參數做啥改變,參數之前的值不會受啥影響,所以anum沒有變成123,仍然是1
 
但是array[0]為啥卻變成10了呢?
參數傳遞,億維訊達
前面我們有說到引用類型在內存中是保存為兩個部分,一個是stack中的內存地址,另一個是heap中的實際值。用時我們只直接用stack中的值,我們假如stack中的值為0xabcdefgh ,就說是array指向它吧。 那么我們按值傳遞時就是把這個stack的值拷貝成另一份就假如是arraynew指向它吧。跟拷貝anum的值1一樣。
 
但是我們操作內存地址這樣的值時不會像整數一樣直接操作它,而只會通過它去找heap中的實際值。
 
于是我們arraynew[0] = 10。改變了實際上還是heap中數組的值了。 但arraynew= new int [] {6,7,8,9}沒有對之前傳的array產生影響。這個操作的意義是在heap中重新開辟一塊內存,保存著值6,7,8,9。 這這塊內存的地址賦給arraynew,于是它之前的值0xabcdefgh被改寫了。但array指的值stack值仍沒變,仍是0xabcdefgh

按引用傳遞參數

 
可以用out或ref顯式指定。它們大部分時候可以通用,只是有一點細小區別。
 
先用ref 來舉例吧,還用上面的例子,只是加個了關鍵字ref
 
class Program
    {
        public static void ChangeInt(ref int num)
        {
            num = 100;
        }
 
        public static void ChangeArray(ref int[] arraynew)
        {
            arraynew[0] = 10;
            arraynew= new int[] { 6, 7, 8, 9 };
        }
 
        static void Main(string[] args)
        {
            int anum = 1;
            int[] array = { 1, 2, 3 };
            ChangeInt(ref anum);
            ChangeArray(ref array);
            Console.WriteLine("value of num: " + anum);
            Console.Write("value of array: ");
            foreach (int i in array)
                Console.Write(i + " ");
        }
 
    }
 
結果是:value of anum : 100
 
       value of array :6 7 8 9
 
跟按值傳遞的結果完全不同吧
 
num = 100我們是容易理解。我們再來說下array的值
 
按引用傳遞時array指向的stack中的值不會復制一份,而是直接傳過去。這樣arraynew[0]= 10這樣賦值時也同樣改變了heap中 1 2 3 的值,變為10 2 3,如果
 
沒有arraynew = new int [] {6,7,8,9} 這個語句,則它的結果跟上面按值傳遞是完全一樣的。但有個這句話后就不一樣,我們知道上面說了它的含義,在heap中開辟一塊新內存
 
值是6 7 8 9,而array指向的stack的值被改寫了,改為指向保存6 7 8 9的內存地址了。那含有10 2 3的那一塊內存其實還繼續存在,只是沒有誰引用到它了。到時垃圾回收器會把它回收的。
 
補充:
 
說下out 和ref的細小區別
 
ref 傳進來的參數必須要先賦值。
 
像上面 的例子中如果這樣寫
 
int num;
 
ChangeInt(ref int num);
 
就會出錯,必須先給num給個值1。
 
而且out傳進來的參數可以不先賦值。
 
out num;
 
ChangeInt(out int num);是對的
 
另外還有個區別就是如果用out的時候ChangeInt函數中必須有某個地方給num賦值了,而用ref不一定需要在函數中給num賦值
 
其實這樣做的目的很好理解。C#為了確保在任何情況下num必須有個值,不能為空。
 
因為用ref,在調用函數前必須保證參數有值,所以在函數中就不必要求它一定再賦值
 
而用out由于在調用函數前不用保證參數必須有值,所以在函數中必須保證給它個值
 
ChangeInt(ref int num)和ChangeInt(out int num)雖然不一樣,但是不同共存,不能當作兩個不同的函數
 
而ChangeInt(int num)和上面 的兩個函數是完全不一樣的,可以放到一起共存
 
這樣的話調用的時候ref ,out這樣的關鍵字不能省的。必須匹配