ref以及傳值傳址的理解
ref(也包括out)關鍵字肯定都會用,傳值調用和傳址調用也是初學寫程式碼時都已經歷過的話題,與這相關的還有一些話題,比如值類型和引用類型有什麼區別等,但是如果不仔細,可能有一些概念的混淆或者理解不夠清晰。本文試圖以最簡單的方式說明一下
比如:對於值類型傳參就是傳值調用,對於引用類型就是傳址調用。如果加上ref關鍵字那就是傳址調用,引用調用時,會改變元參數值,看上去好像是的,看一個例子:
public class MyClass{ public int Id { get; set; } } static void Invoke1(MyClass myClass) { myClass.Id = 0; } static void Invoke2(MyClass myClass) { myClass = new MyClass { Id = 50 }; } var myClass = new MyClass { Id = 100 };//原始值100 Invoke1(myClass); Console.WriteLine(myClass.Id); //100變為0 Invoke2(myClass); Console.WriteLine(myClass.Id); //依然是0
下面換一下將引用類型的參數加上ref關鍵字
public class MyClass{ public int Id { get; set; } } static void Invoke1(MyClass myClass) { myClass.Id = 0; } static void Invoke2(ref MyClass myClass) { myClass = new MyClass { Id = 50 }; } var myClass = new MyClass { Id = 100 };//原始值100 Invoke1(myClass); Console.WriteLine(myClass.Id); //100變為0 Invoke2(ref myClass); Console.WriteLine(myClass.Id); //0變為50
這裡的現象是:
- 引用類型的參數,函數中的改變不一定會影響原來的參數
- 即使是引用類型,加上ref關鍵字以後也可能產生不一樣的結果
那麼ref關鍵字 和 傳址調用還不是一回事,那怎麼理解?
正常情況下(沒有ref等關鍵字)的傳參是怎麼傳的(包括引用類型和值類型)?
答案:傳棧的副本
不管是值類型還是引用類型:
傳過去的都是棧的副本:新的棧地址(棧的地址有改變) + 值副本(完全不變)
那麼引用類型和值類型的參數傳參行為是有區別的,區別在這裡:
- 對於值類型:值副本就是原來的值
- 對於引用類型:值副本就是原來的堆棧地址
PS: 值類型棧上保存的值,引用類型棧上保存的託管堆的地址,真正的值在託管堆上
值類型傳參對原參數無影響:棧地址和棧上的值都是副本,當然沒影響
引用類型為什麼有影響(不是所有情況都有影響):傳過去的堆棧地址和原來的堆棧地址是同一個地址,引用類型數據在堆棧,所以操作是針對的同一個堆棧操作,堆棧值變了,原參數引用的也是這個堆棧,當然值也跟著變化。但是如果這種操作不是操作堆棧則不會影響以前的數據(比如把棧地址副本指向一個新的堆棧地址),
myClass = new MyClass { Id = 50 };
,這種操作是在堆棧上重新分配地址,然後把堆棧地址賦值給新棧副本,也就是副本棧的值不是原來的堆棧地址了,而是新的堆棧地址,那麼這種改變對於原來的棧地址是沒有任何影響的。
正常傳參過程中值類型和引用類型記憶體示意圖:

那麼ref關鍵字到底是有什麼作用?
答案:傳參數棧
PS: 不是傳棧副本,而是參數棧,那麼一切都好理解了,out也是一樣的,只不過必須要賦值或者指向堆棧。但是這種情況又不一樣 :Method(out var parameters),有興趣可以看一下資料
為什麼string類型是傳值調用?
答案:string類型傳參沒任何特殊性,特殊性在於string類型的操作都是開闢新的堆棧,而不是改變原來堆棧值(string類型是比較特殊的引用類型,重寫了一些方法和行為,這是另外一個話題)