ref以及傳值傳址的理解
阿新 • • 發佈:2020-07-30
ref(也包括out)關鍵字肯定都會用,傳值呼叫和傳址呼叫也是初學寫程式碼時都已經歷過的話題,與這相關的還有一些話題,比如值型別和引用型別有什麼區別等,但是如果不仔細,可能有一些概念的混淆或者理解不夠清晰(引用型別引數加ref關鍵字是多餘的嗎),本文試圖以最簡單的方式說明一下
有一些常見的說法:對於值型別傳參就是傳值呼叫,對於引用型別就是傳址呼叫。如果加上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); //這裡加了ref Console.WriteLine(myClass.Id); //結果變了:0變為50
- 引用型別的引數,函式中的改變不一定會影響原來的引數
- 即使是引用型別,加上ref關鍵字以後也可能產生不一樣的結果
那麼引用型別和值型別的引數傳參行為是有區別的,區別在於值型別和引用型別的儲存方式:
- 對於值型別:值副本就是原來的值
- 對於引用型別:值副本就是原來的堆疊地址
PS: 值型別棧上儲存的值,引用型別棧上儲存的託管堆的地址,真正的值在託管堆上
值型別傳參對原引數無影響:棧地址和棧上的值都是副本,當然沒影響
引用型別為什麼有影響(不是所有情況都有影響):傳過去的堆疊地址和原來的堆疊地址是同一個地址,引用型別資料在堆疊,所以操作是針對的同一個堆疊操作,堆疊值變了,原引數引用的也是這個堆疊,當然值也跟著變化。但是如果這種操作不是操作堆疊則不會影響以前的資料,比如把棧地址副本指向一個新的堆疊地址:
myClass = new MyClass { Id = 50 };
,這種操作是在堆疊上重新分配地址,然後把堆疊地址賦值給新棧副本,也就是副本棧的值不是原來的堆疊地址了,而是新的堆疊地址,那麼這種改變對於原來的棧地址是沒有任何影響的。
正常傳參過程中值型別和引用型別記憶體示意圖:
那麼ref關鍵字到底是有什麼作用? 答案:傳引數棧 PS: 不是傳棧副本,而是引數棧,那麼一切都好理解了,out也是一樣的,只不過必須要賦值或者指向堆疊。但是這種情況又不一樣 :Method(out var parameters),有興趣可以看一下資料 為什麼string型別是傳值呼叫? 答案:string型別傳參沒任何特殊性,特殊性在於string型別的操作都是開闢新的堆疊,而不是改變原來堆疊值(string型別是比較特殊的引用型別,重寫了一些方法和行為,這是另外一個話題)