通过一个实例重新认识引用类型,值类型,数组,堆栈,ref
昨天在写代码时候遇到了一个问题,百思不得其解,感觉颠覆了自己对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 = , Name = ""}, new Test() {Age = , Name = ""}}; 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 = , Name = "" }, new Test() { Age = , Name = "" } };
Test1(ps[]);
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 = , Name = "" }, new Test() { Age = , Name = "" } };
Test2(ps[]);
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 = , Name = "" }, new Test() { Age = , Name = "" } };
Test3(ref ps[]);
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 = , Name = "" };
} private static void Test2(Test t)
{
t.Name = "";
t.Age = ;
} private static void Test3(ref Test t)
{
t = new Test() { Age = , Name = "" };
}
}
}
这个例子比较简单,要实现的功能就是为对象数组中的某一个元素赋值。
我遇到的问题相当于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)里的大家提供的帮助,谢谢大家!
通过一个实例重新认识引用类型,值类型,数组,堆栈,ref的更多相关文章
- [Clr via C#读书笔记]Cp5基元类型引用类型值类型
Cp5基元类型引用类型值类型 基元类型 编译器直接支持的类型,基元类型直接映射到FCL中存在的类型. 作者希望使用FCL类型名称而避免使用关键字.他的理由是为了更加的清晰的知道自己写的类型是哪种.但是 ...
- 值类型前加ref和out的区别
1.值类型前加ref,在调用前必须先初始化,初始化之后在方法内部直接使用 值类型x前加了ref,方法外的x会随着方法内的x改变而改变,因为此时传的是地址,如下面的例子, x前加了ref所以x = x+ ...
- JavaScript高级 面向对象(12)--引用类型值类型作为参数传递的特性
说明(2017-4-2 18:27:11): 1. 作为函数的参数,就是将函数的数据拷贝一份,传递给函数的定义中的参数. 函数foo()在调用的时候,做了两件事: (1)函数在调用的时候,首先需要将参 ...
- C# - 值类型、引用类型&走出误区,容易错误的说法
1. 值类型与引用类型小总结 1)对于引用类型的表达式(如一个变量),它的值是一个引用,而非对象. 2)引用就像URL,是允许你访问真实信息的一小片数据. 3)对于值类型的表达式,它的值是实际的数据. ...
- 深入C#内存管理来分析值类型&引用类型,装箱&拆箱,堆栈几个概念组合之间的区别
C#初学者经常被问的几道辨析题,值类型与引用类型,装箱与拆箱,堆栈,这几个概念组合之间区别,看完此篇应该可以解惑. 俗话说,用思想编程的是文艺程序猿,用经验编程的是普通程序猿,用复制粘贴编程的是2B程 ...
- C# 值类型和引用类型
一.基本概念 C#只有两种数据类型:值类型和引用类型 值类型在线程栈分配空间,引用类型在托管堆分配空间 值类型转为引用类型称成为装箱,引用类型转为值类型称为拆箱 以下是值类型和引用类型对照表 从上图可 ...
- CLR via C#(02)-基元类型、引用类型、值类型
http://www.cnblogs.com/qq0827/p/3281150.html 一. 基元类型 编译器能够直接支持的数据类型叫做基元类型.例如int, string等.基元类型和.NET框架 ...
- C#如何更好地理解引用类型和值类型
说道值类型和引用类型,在C#中,官方的说法就是: 值类型直接指向数据:一般包括C#自带的所有数字类型,字符类型,bool类型,当然还有自定义的结构类型和枚举类型 而引用类型则是指向数据存储的地址.一般 ...
- 【C#进阶系列】05 基元类型、引用类型和值类型
基元类型和FCL类型 FCL类型就是指Int32这种类型,这是CLR支持的类型. 而基元类型就是指int这种类型,这是C#编译器支持的,实际上在编译后,还是会被转为Int32类型. 而且学过C的朋友 ...
随机推荐
- Android Service
一.在MainAcitivity界面启动Service : public class MyService extends Service intent = new Intent(MainActivi ...
- [更新设计]跨平台物联网通讯框架ServerSuperIO 2.0 ,功能、BUG、细节说明,以及升级思考过程!
注:ServerSuperIO 2.0 还没有提交到开源社区,在内部测试!!! 1. ServerSuperIO(SSIO)说明 SSIO是基于早期工业现场300波特率通讯传输应用场景发展.演化而来. ...
- 10分钟写一个markdown编辑器
marked.js Marked是一个Markdown解析引擎. vue.js Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的 渐进式框架.与其他重量级框架不同的是,Vu ...
- getting started with transformjs
Introduction In the past two years, more and more friends for mobile web development have used the t ...
- BW知识问答汇总
什么是sap的星型结构,能不能详细讲解一下? Cube的星型结构中SID技术的优点有哪些? 什么是BW的星型结构,与传统的星型结构的区别是什么? SAP的星型结构相对于传统的星型结构优势? Cube与 ...
- [Android]Android端ORM框架——RapidORM(v2.1)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6020412.html [Android]Android端ORM ...
- iOS10 适配问题-Xcode8
前段时间升级了Xcode8,整体来说对OC的影响不大,但是还是跳一个坑,消耗了不少时间.这里总结下遇到的适配问题. 1.权限问题 Xcode8 访问相机.相册等需要权限的地方崩溃 解决办法: 在使用私 ...
- 设置这些之后,Google突然可以打开了
打开的是:https://www.google.com.hk
- sublime 相关配置
安转插件:案例格式化HTML代码,需要安装插件,具体安装步骤如下: 1.打开菜单->首选项->插件控制,输入 install package 2.等待程序进入插件管理功能,再输入插件名称: ...
- Atitit.css 规范 bem 项目中 CSS 的组织和管理
Atitit.css 规范 bem 项目中 CSS 的组织和管理 1. 什么是BEM?1 1.1. 块(Block)2 1.2. 元素(Element)2 1.3. BEM树(和DOM树类似).3 ...