文档章节

通过一个实例重新认识引用类型,值类型,数组,堆栈,ref

独行族妖侠
 独行族妖侠
发布于 2016/10/12 09:11
字数 1278
阅读 42
收藏 0

        昨天在写代码时候遇到了一个问题,百思不得其解,感觉颠覆了自己对C#基础知识的认知,因为具体的情境涉及公司代码不便放出,我在这里举个例子,先上整个测试所有的代码,然后一一讲解我的思考过程:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var ps = new Test[] {new Test() {Age = 1, Name = "1"}, new Test() {Age = 5, Name = "5"}};

            Console.WriteLine("原始数组");
            foreach (var m in ps)
            {
                Console.WriteLine("Name="+m.Name+"Age="+m.Age);
            }
            Console.WriteLine("================================");

            Console.WriteLine(@"private static void Test1(Test t)
        {
            t = new Test() { Age = 4, Name = 4 };
        }");
            ps = new Test[] { new Test() { Age = 1, Name = "1" }, new Test() { Age = 5, Name = "5" } };
            Test1(ps[0]);
            foreach (var m in ps)
            {
                Console.WriteLine("Name=" + m.Name + "Age=" + m.Age);
            }
            Console.WriteLine("================================");

            Console.WriteLine(@"private static void Test2(Test t)
        {
                t.Name = 4;
                t.Age = 4;
            }
            ");
            ps = new Test[] { new Test() { Age = 1, Name = "1" }, new Test() { Age = 5, Name = "5" } };
            Test2(ps[0]);
            foreach (var m in ps)
            {
                Console.WriteLine("Name=" + m.Name + "Age=" + m.Age);
            }
            Console.WriteLine("================================");

            Console.WriteLine(@"private static void Test3(ref Test t)
        {
            t = new Test() { Age = 4, Name = 4 };
        }
            ");
            ps = new Test[] { new Test() { Age = 1, Name = "1" }, new Test() { Age = 5, Name = "5" } };
            Test3(ref ps[0]);
            foreach (var m in ps)
            {
                Console.WriteLine("Name=" + m.Name + "Age=" + m.Age);
            }
            Console.WriteLine("================================");

            Console.ReadKey();

        }

        class  Test
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }

        private static void Test1(Test t)
        {
            t = new Test() { Age = 4, Name = "4" };
        }

        private static void Test2(Test t)
        {
            t.Name = "4";
            t.Age = 4;
        }

        private static void Test3(ref Test t)
        {
            t = new Test() { Age = 4, Name = "4" };
        }
    }
}

        这个例子比较简单,要实现的功能就是为对象数组中的某一个元素赋值。

  我遇到的问题相当于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)里的大家提供的帮助,谢谢大家!

© 著作权归作者所有

独行族妖侠
粉丝 5
博文 3
码字总数 4140
作品 1
太原
程序员
私信 提问
加载中

评论(2)

fscad
fscad
另外这个:"而与数组元素毫无关系了,所以数组元素经过Test2函数后值是不变的。"有笔误应为Test1吧
fscad
fscad
学习了.
1.如果不使用Ref或Out关键字. CRL默认都是传值.(引用本质上也是个值)
2.Ref和Out关键字只有在需要"更新"引用时采有意义.
c#语法复习总结(2)-数据类型

C#数据类型可以分值类型和引用类型。值类型,先说说一个概念 c#栈和堆. 一,栈和堆. 堆:在c里面叫堆,在c#里面其实叫托管堆。为什么叫托管堆,我们往下看。 栈:就是堆栈,因为和堆一起叫着...

在神
05/06
0
0
C# 中的结构类型(struct type)

ylbtech- .NET-Basic:C# 中的结构类型(struct type) C# 中的结构类型(struct type) 1.A,相关概念返回顶部   像类一样,结构(struct)是能够包含数据成员和函数成员的数据结构,但是与...

吞吞吐吐的
2017/11/01
0
0
Android破解学习之路(十六)—— dll破解的IL指令

IL指令介绍 IL是.NET框架中中间语言(Intermediate Language)的缩写。 使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器...

Stars-one
02/13
0
0
C#语言struct结构体适用场景和注意事项

在C#语言中struct结构体和class之间的区别主要是值类型和引用类型的区别,但实际上如果使用不当是非常要命的。从Win32时代过来的人对于struct一点不感觉陌生,但是却反而忽略了一些基本问题。...

炽火
2016/09/05
66
0
[译]聊聊C#中的泛型的使用(新手勿入)

写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发现了一些问题,因此也进行了...

依乐祝
2018/11/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

重定向又是什么“垃圾”——教你再分类

  前言:之前写了几篇JSP的博客,现在又回过头来看Servlet,温故而知新,再回顾回顾,总会有收获的。以前学习Servlet感觉内容很多,现在看的时候,其实也没多少东西,只需知道Servlet的生命...

SEOwhywhy
1分钟前
0
0
一图胜千言!这10种可视化技术你必须知道

全文共4549字,预计学习时长9分钟 图片来源:Willian Justen deVasconcellos on Unsplash 相比于浩如烟海的数据表格,大部分人还是更喜欢视觉资料,这一点已不足为奇。也是出于这个原因,人们...

读芯术
5分钟前
0
0
Spring Boot 自动配置(auto-configurtion) 揭秘

本章,我们为你揭秘Spring Boot自动配置(Auto Configuration)运行机制,谈到auto-configuration,肯定离不开@EnableAutoConfiguration注解。 package org.springframework.boot.autoconfi...

爱编程的浪子
11分钟前
0
0
RabbitMQ延迟消息的延迟极限是多少?

之前在写Spring Cloud Stream专题内容的时候,特地介绍了一下如何使用RabbitMQ的延迟消息来实现定时任务。最近正好因为开发碰到了使用过程中发现,延迟消息没有效果,消息直接就被消费了的情...

程序猿DD
11分钟前
0
0
MySQL知识库语雀

类型:所有 MySQL知识库 MySQL知识库 06-05 20:57 近期阅读文章 近期已读或者未读文章列表 03-08 10:49 日常脚本 这里是平时工作会用到的一些常用的脚本,作为统一管理 03-08 05:09 培训 03...

rootliu
14分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部