通过一个实例重新认识引用类型,值类型,数组,堆栈,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的朋友 ...
随机推荐
- jQuery的$.ajax
在介绍JSONP之前,先简单的介绍一些JSON.JSON是JavaScript Object Notation的缩写,是一种轻量的.可读的基于文本的数据交换开放标准.源于JavsScript编程语言中 ...
- 腾讯AlloyTeam移动Web裁剪组件AlloyCrop正式开源
传送门 Github地址:https://github.com/AlloyTeam/AlloyFinger/tree/master/alloy_crop 在线Demo演示: 简介 裁剪图片的应用场景有 ...
- 前端导出Excel兼容写法
今天整理出在Web前端导出Excel的写法,写了一个工具类,对各个浏览器进行了兼容. 首先,导出的数据来源可能有两种: 1. 页面的HTML内容(一般是table) 2. 纯数据 PS:不同的数据源, ...
- 利用BI搭建零售业数据信息平台
某百货公司是全市规模最大的以零售为主.多元化经营的股份制商业企业.拥有员工数千人,经营国内外品牌2300余种,年商品销售额逾10亿人元. 销售体量如此庞大的企业近几年在IT建设上出现了问题,集团内部的 ...
- SAP CRM 客户控制器与数据绑定
当用户从视图离开时,视图将失去它的数据.解决这个问题,需要引入客户控制器(Custom Controller)(译者注:SAP CRM客户端中,不同地方的Custom Controller会翻译为“客 ...
- [转]ASP.NET应用程序生命周期趣谈(二)
在上回书开始的时候我们提到博客园的IIS看了一眼我的请求后就直接交给ASP.NET去处理了,并且要求ASP.NET处理完之后返回HTML以供展示. 那么我们不仅要问: 1, IIS肯定是没有眼睛 ...
- Normalize.css的使用及下载
Normalize.css 只是一个很小的CSS文件,但它在默认的HTML元素样式上提供了跨浏览器的高度一致性.相比于传统的CSS reset,Normalize.css是一种现代的.为HTML5准备 ...
- oncopy="document.selection.empty()"跟oncopy="return false"什么区别?
实现效果一样,禁止复制. 区别: oncopy="document.selection.empty()" 没禁止,只是把它复制的内容,变成空了: oncopy="ret ...
- Oracle 用户、角色、权限(系统权限、对象权限)的数据字典表
1 三者的字典表 1.1 用户 select * from dba_users; select * from all_users; select * from user_users; 1.2 角色 s ...
- 版本控制-svn服务器搭建和常用命令(centos 6.3)
Svn是比较优秀的版本控制工具,虽然功能和性能上无法和Git媲美,但由于其容易搭建和使用的特性,所以在各个小公司还是很受欢迎的.使用Git可参考<版本控制-Git服务器搭建和常用命令使用> ...