昨天在寫程式碼時候遇到了一個問題,百思不得其解,感覺顛覆了自己對C#基礎知識的認知,因為具體的情境涉及公司程式碼不便放出,我在這裡舉個例子,先上整個測試所有的程式碼,然後一一講解我的思考過程:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace ConsoleApplication1 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 var ps = new Test[] {new Test() {Age = 1, Name = "1"}, new Test() {Age = 5, Name = "5"}}; 12 13 Console.WriteLine("原始陣列"); 14 foreach (var m in ps) 15 { 16 Console.WriteLine("Name="+m.Name+"Age="+m.Age); 17 } 18 Console.WriteLine("================================"); 19 20 Console.WriteLine(@"private static void Test1(Test t) 21 { 22 t = new Test() { Age = 4, Name = 4 }; 23 }"); 24 ps = new Test[] { new Test() { Age = 1, Name = "1" }, new Test() { Age = 5, Name = "5" } }; 25 Test1(ps[0]); 26 foreach (var m in ps) 27 { 28 Console.WriteLine("Name=" + m.Name + "Age=" + m.Age); 29 } 30 Console.WriteLine("================================"); 31 32 Console.WriteLine(@"private static void Test2(Test t) 33 { 34 t.Name = 4; 35 t.Age = 4; 36 } 37 "); 38 ps = new Test[] { new Test() { Age = 1, Name = "1" }, new Test() { Age = 5, Name = "5" } }; 39 Test2(ps[0]); 40 foreach (var m in ps) 41 { 42 Console.WriteLine("Name=" + m.Name + "Age=" + m.Age); 43 } 44 Console.WriteLine("================================"); 45 46 Console.WriteLine(@"private static void Test3(ref Test t) 47 { 48 t = new Test() { Age = 4, Name = 4 }; 49 } 50 "); 51 ps = new Test[] { new Test() { Age = 1, Name = "1" }, new Test() { Age = 5, Name = "5" } }; 52 Test3(ref ps[0]); 53 foreach (var m in ps) 54 { 55 Console.WriteLine("Name=" + m.Name + "Age=" + m.Age); 56 } 57 Console.WriteLine("================================"); 58 59 Console.ReadKey(); 60 61 } 62 63 class Test 64 { 65 public string Name { get; set; } 66 public int Age { get; set; } 67 } 68 69 private static void Test1(Test t) 70 { 71 t = new Test() { Age = 4, Name = "4" }; 72 } 73 74 private static void Test2(Test t) 75 { 76 t.Name = "4"; 77 t.Age = 4; 78 } 79 80 private static void Test3(ref Test t) 81 { 82 t = new Test() { Age = 4, Name = "4" }; 83 } 84 } 85 }
這個例子比較簡單,要實現的功能就是為物件陣列中的某一個元素賦值。
我遇到的問題相當於Test1函式,將陣列的元素傳入Test1之後,判斷,如果不符合要求就new一個新的物件,於是,問題來了。除錯發現,新new的物件並沒有真的替換掉陣列中對應的元素,有違常理啊,一個引用型別引數傳入函式,函式中修改物件的值應該是會體現在源物件上的,為啥值沒變呢?
其實,這個理解也沒錯,但是有個前提,就是不new一個新物件賦值給引數的情況下,如Test2函式的做法,這樣是會改變物件值的。
為什麼會這樣呢?必須先承認自己的基礎知識太差了。
我們知道,引用型別的引用(類似指標)是存放在棧地址中的,而它真是的值是存放在堆地址中的,值型別沒有引用,它的值直接存放在棧地址中。Test2函式之所以能改變主函式中陣列元素的值是因為形參t傳入了陣列元素的引用,這個引用指向它對應的值的地址,直接修改t的值,其實也是在直接修改陣列元素的值,形參t只傳遞了引用,而值還是與陣列元素的共用一個的。但在Test1函式中就不一樣了,t作為形參傳入了陣列元素的引用,在函式中又重新new了一個物件,這就意味著,t所代表的引用已經從原來的陣列元素變為了新物件的引用,對t的值進行修改只會影響新物件,而與陣列元素毫無關係了,所以陣列元素經過Test2函式後值是不變的。
既然存在這個問題,但是函式又不可能大改,畢竟牽一髮而帶動全身,那怎麼辦呢?
很簡單為形參t加一個ref修飾,於是就成了Test3函式,Test3函式可以做到就算new一個新物件,也會改變陣列元素的值。
這是為什麼呢?要搞清這個我們必須重新理解一下ref。
看到我這個使用方式,很多人第一反應是ref不是給值型別用的,給引用型別用ref是幾個意思?其實不然,ref也可以給引用型別用,而且是有意義的。ref的本質是直接傳遞棧地址,值型別的值本身就放在棧地址中,所以ref對值型別起作用。對於引用型別,我們之前提到了,引用型別的引用(類似指標)是存放在棧地址中的,而它真是的值是存放在堆地址中的,在函式中,形參t傳遞的其實只是陣列元素的引用,也就是引用型別的棧地址部分,如果對引用型別使用ref就意味著,不管你在函式裡面是修改引用型別的值,還是引用,它都直接返回t當前的引用,而引用型別又是通過引用找到值,於是,就算你new一個新的物件,主函式中的陣列元素的值也會跟著改變,因為陣列元素的引用因為ref的存在而改變了。
有些基礎知識雖然枯燥,但是一旦遇到了就會知道它的重要性,還是需要好好學習啊!
最後,感謝深藍醫生在這個過程中提供的幫助,還有SOD框架高階群(18215717)裡的大家提供的幫助,謝謝大家!