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

来源:互联网 发布:邻里家人网络平台 编辑:程序博客网 时间:2024/06/10 14:09

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

 

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

 

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

 

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

 

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

 

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

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

 

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

 

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

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

 

看看另一个方法...

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

 

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

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

 

其实这个问题实在是太简单,看两眼MSDN就清清楚楚了...我都不好意思写这篇文章,有凑篇数的嫌疑,但是你要经常看看.NET版那些回答就知道这种问题实在是太普遍了...

原创粉丝点击