昨天在寫代碼時候遇到了一個問題,百思不得其解,感覺顛覆了自己對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)里的大家提供的幫助,謝謝大家!