通過一個實例重新認識引用類型,值類型,數組,堆棧,ref


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM