C#引數的按值傳遞和按參照傳遞

2020-07-16 10:04:47
一個方法可以包括 0 或多個引數。在方法前面括號中規定的參數列稱為形參,而傳遞進方法的引數稱為實參。

C# 的預設方式是按值傳遞(pass by value),若傳遞物件是值型別,則按值傳遞之後,傳遞進方法的不過是值的副本而已,方法外部的物件不受影響。

按參照傳遞(pass by reference)之後,傳遞進方法的是值型別的地址,方法外部的物件會受影響。

實際上,按參照傳遞是按值傳遞的一種特殊情況(參照是地址,地址也是值)。

例如一個試圖調換 a 和 b 值的 Swap 函數,如果其簽名為 void Swap(int a, int b),則如果在某處呼叫該函數,呼叫完畢後,a 和 b 的值是不會變的。

因為,傳入時會自動複製兩個 int 副本,函數內部的任何操作和 a,b 都沒有關係。

如果其簽名是 void Swap(ref int a, ref int b),,則改為傳入 a 和 b 的地址,不會發生複製,呼叫該函數將確實會調換 a 和 b 的值。

如果傳遞物件是參照型別,則無論是普通的傳遞還是加上 ref 和 out 關鍵字,都會更改方法外部的物件。

加不加 ref 和 out 關鍵字的區別在於是否可以把參照重新賦值給一個新的物件(即更改傳入的地址本身)。

ref 和 out 的區別是,ref 需要事先賦值,而 out 必須在方法返回之前賦值。

另外,一個方法可以有多個由 ref 或 out 修飾的輸入變數,這間接地令方法有了多個返回值。

如果兩個方法僅僅在 ref 和 out 上有區別(相同名稱,相同輸岀,相同輸入),則在編譯器看來他們是相同的方法。

特殊情況:如果傳遞的物件是字串,則其行為類似值型別(字串的值不改變)。要 傳遞參照,必須要加 ref 關鍵字才可以。

例如 void Swap(string a, string b),呼叫完畢後,外 部字串是不會被交換的。

為了驗證參照型別的按參照傳遞,我們來看下面的程式碼:
public class AClass
{
    public int a;
    public string b;
    public static void ChangeValue(AClass a)
    {
        a.a = 111;
        var b = new AClass();
        b.a = 222;
        a = b;
    }
    public static void ChangeValueRef(ref AClass a)
    {
        a.a = 888;
        var b = new AClass();
        b.a = 999;
        a = b;
    }
}
當我們呼叫時:
static void Main(string[] args)
{
    var a = new AClass();
    a.a = 1;
    AClass.ChangeValue(a);
    Console.WriteLine(a.a);
    AClass.ChangeValueRef(ref a);
    Console.WriteLine(a.a);
    Console.ReadKey();
}
會輸出 111 和 999。我們發現,按值傳遞時,參照型別棧上的地址不能改變;而按參照傳遞時,可以改變棧上的地址,指向一個新的堆上的物件。

在看完上面的例子之後,理解字串的行為可能就容易了一些。看下面的程式碼:
static void Main(string[] args)
{
    var s = "test";
    Change(s);
    Console.WriteLine(s);
    Console.WriteLine(Change2(s));

    ChangeRef(res s);
    COnsole.ReadKey();
}
public static void Change(string s)
{
    s += "1";
}
public static void Change2(string s)
{
    return s += "2";
}
public static void ChangeRef(ref string s)
{
    s += "3";
}
當呼叫 Change 方法之後,字串池中有兩個字串,分別為 test 和 test 1(大部分情況下,字串操作每次都產生新的字串)。

但是,由於沒有更改 s 本身參照的指向(令 s 等於它),所以 test1 實際上沒有參照指向它,方法結束之後,它就變成了垃圾。

此時,外部的 s 仍然指向 test,所以,s 的值不發生變化。這和普通的參照型別按值傳遞不同,因為,普通的參照型別沒有不變性,不會在操作時產生新的物件。

Change2 方法則返回一個值,所以列印岀的值發生了變化,s 現在指向一個新的值。此時,記憶體中又增加了一個字串 test2。

Change3 方法傳遞參照,所以可以把字串 s 賦值給一個新的物件 test3。這和普通的參照型別按參照傳遞相同。