• 通过一个实例重新认识引用类型,值类型,数组,堆栈,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)里的大家提供的帮助,谢谢大家!

  • 相关阅读:
    Html5 Input 类型
    Html5 部分特性
    Asp.net Mvc4 基于Authorize实现的模块访问权限
    第11天知识点总结
    C# string类型和byte[]类型相互转换
    C#中AppDomain.CurrentDomain.BaseDirectory及各种路径获取方法
    Socket 学习
    C#中的Dictionary字典类介绍
    js判断客户端是pc还是手机
    input type="file" accept="image/*"上传文件慢的问题解决办法
  • 原文地址:https://www.cnblogs.com/znlgis/p/5951508.html
Copyright © 2020-2023  润新知