【.NET版月经问题】之二【引用类型参数就是按引用传递吗?】

简介:

这个问题其实算不得月经帖问题,已经成周经甚至日经了...隔几天就有人问,然后总是大部分人站在自以为正确的角度大谈引用与指针的相似性,假如不幸这参数是string类型无一例外会被人扯到string的“不可变性”上去,而且每次都有扛着红星星的高人大撒烟雾弹,让提问者一头雾水...

这问题产生的来源是可恶的C/C++指针...很多人指点新手(包括很多培训机构的讲师)讲到“引用”这一.NET的特殊数据类型时,都直接一句:相当于(甚至说“就是”)C/C++的指针——其实我也说过,惭愧...不幸的是两者看似相似,本质却不同...事实上这个引用和Java的引用才是非常相似的东西,几乎一样...

这就带来一个问题...凡是学过C/C++指针的,总有很多人想当然地认为传递一个引用类型的参数就是传递这个引用类型实例的引用——我还没说指针呢——看起来似乎是这样...大错特错!

在.NET中,除非显式以ref或out声明传递参数,否则所有类型的参数都是按值传递的!引用类型也不会例外!

 

 

这句话我不知道回答过多少遍,不过看起来没什么效果...每次一出此类问题,照旧大堆人往按引用传递和string不可变上引...

那么先来看看实例吧...值类型一般不会有疑义,就不提了...

view plaincopy to clipboardprint?

  1. class test 
  2. public string s = null; 
  3. public int i = 0; 
  4.  
  5. public void run() 
  6.     test instance = new test(); 
  7.     instance.s = "first"; 
  8.     instance.i = 1; 
  9.     callByValue(instance); 
  10.  
  11. public void callByValue(test t) 
  12.     t.s = "changed"; 
  13.     t.i = 2; 

class test { public string s = null; public int i = 0; } public void run() { test instance = new test(); instance.s = "first"; instance.i = 1; callByValue(instance); } public void callByValue(test t) { t.s = "changed"; t.i = 2; }

这段代码就是广泛会被误认为按引用传递实际上是按值传递的参数传递方式...它的执行结果非常明显,参数引用类型test的实例instance的成员s和i一定会被更改,所以看起来它似乎确实是按引用传递的...但是,错的!这个参数传递的是该参数实例的一个副本!

引用类型实例的副本是什么呢?就是这个instance的引用的副本...也就是说,这个时候在栈上,原来的instance的引用还在,传递给callByValue方法的参数t是栈上instance的引用的copy...这个copy引用指向托管堆上instance值的地址,所以一改俱改...所以表象似乎一样,但和C/C++传递指针的方式本质是差别巨大的...

我们把callByValue方法稍作修改,如下...

view plaincopy to clipboardprint?

  1. public void callByValue(test t) 
  2.     test tmp=new test(); 
  3.     tmp.s = "changed"; 
  4.     tmp.i = 2; 
  5.     t = tmp; 

public void callByValue(test t) { test tmp=new test(); tmp.s = "changed"; tmp.i = 2; t = tmp; }

结果很显然,不会变...instance和instance的成员都不会变,原因呢?最常见的解释是作用域不同,tmp只在callByValue方法体中存活,所以呢,出了这个方法体就不起作用了...胡说八道!出了方法体tmp废弃了,那instance应该是null才对啊?!什么思维逻辑...其实很简单,上面说了,这个传递进来的引用只是个副本,修改这个副本不会对在栈上的instance引用有丝毫影响,新构造的实例也跟托管堆上instance的值毫不相干...当退出方法体时,这个副本随即被当作垃圾废弃,instance和它的成员自然不会变...

看看另一个方法...

view plaincopy to clipboardprint?

  1. public void callByReference(ref test t) 
  2.     test tmp=new test(); 
  3.     tmp.s = "changed"; 
  4.     tmp.i = 2; 
  5.     t = tmp; 

public void callByReference(ref test t) { test tmp=new test(); tmp.s = "changed"; tmp.i = 2; t = tmp; }

这就改变了...tmp被废弃了,但是instance却改变了...因为这时传递进去的是instance的引用本身,自然在t=tmp;时instance的引用被修改指向tmp在托管堆上的值...

最后来看看“特殊”的string...在被当做参数传递时,string一点也不特殊...

view plaincopy to clipboardprint?

  1. public void run() 
  2. string instance = "first"; 
  3.     callByValue(instance); 
  4.     callByReference(instance); 
  5.  
  6. public void callByValue(string s) 
  7.     s = "changed"; 
  8.  
  9. public void callByReference(ref string s) 
  10.     s = "changed"; 

public void run() { string instance = "first"; callByValue(instance); callByReference(instance); } public void callByValue(string s) { s = "changed"; } public void callByReference(ref string s) { s = "changed"; }

callByValue方法一定不会更改instance的值,而callByReference方法一定会更改instance的值...原因和上面一样,前者传递的是instance的引用的副本,后者传递的是instance的引用本身...跟什么“不可变性”、“字符串驻留”毫不相干...

此处存疑:

此处有个问题,string传到下面方法的时候,确实是传递了一个引用的副本。 
public void callByValue(string s)  
{  
    s = "changed";  
}

那么既然是引用的副本,那么指向的实际内容就必定是是同一个地址,同一个数据。然后再对这个引用副本赋值,理论上是指向的内容会被更改,但是实际上,正是由于string的特殊性,他不能直接在该地址空间上做修改,而只能开辟新空间,所以导致引用副本指向新开辟的空间。

所以这里和string的特殊性是有关系的。
















本文转自cnn23711151CTO博客,原文链接:http://blog.51cto.com/cnn237111/601458 ,如需转载请自行联系原作者



相关文章
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
259 6
|
开发框架 前端开发 .NET
Asp.net Webapi 的 Post 方法不能把参数加到 URL 中?试试这样写
Asp.net Webapi 的 Post 方法不能把参数加到 URL 中?试试这样写
410 0
|
机器学习/深度学习 存储 编解码
多任务学习新篇章 | EMA-Net利用Cross-Task Affinity实现参数高效的高性能预测
多任务学习新篇章 | EMA-Net利用Cross-Task Affinity实现参数高效的高性能预测
718 0
|
程序员 数据库
VB.NET—Bug调试(参数话查询、附近语法错误)
VB.NET—Bug调试(参数话查询、附近语法错误)
289 0
JavaFX-MediaPlayer 参数URI格式问题:java.net.URISyntaxException: Illegal character in path at index X
JavaFX-MediaPlayer 参数URI格式问题:java.net.URISyntaxException: Illegal character in path at index X
|
前端开发 JavaScript
.net core 前端传递参数有值 后端接收到的数据却是null
1、问题分析 在做接口测试时,偶然出现了前端输出有值,但是后端断点调试时却出现接收参数总是为null的情况 2、解决办法 前端打印log,看前端的每一个传值的数据类型,与后端请求参数类进行认真的一一比对 小技巧: ① 直接打印调用接口的传参值的数据类型,例如 console.log(type of this.form.name) --string console.log(type of this.form.age) --number 打印的数据类型与后端接口的参数类比对,查出不对应的类型 ② 关于非必填的值,默认传值可能出现空字符串(' ')、NaN值(Not a Number
844 0
|
C++
【.Net】使用委托实现被引用的项目向上级项目的消息传递事件
在实际项目过程中,经常可能遇到被引用的项目要向上传递消息,但是又不能通过方法进行返回等操作,这个时候委托就派上用场了。以下使用委托,来实现被引用的项目向上传递消息的小教程,欢迎各位大佬提供建议。1、新增控制台项目(一般在CS架构中会用的比较多,用于跨线程传递消息使用)。此处用一个控制台项目来模拟演示使用委托进行消息事件的传递教程。
324 0
【.Net】使用委托实现被引用的项目向上级项目的消息传递事件
解决.NET Core Ajax请求后台传送参数过大请求失败问题
解决.NET Core Ajax请求后台传送参数过大请求失败问题
|
前端开发
.NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
.NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
860 0
.NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数