C#值传递引用类型时new不覆盖原对象及传值传引用的原理疑问
搞懂C#里的按值传递与引用类型的那些坑
我太懂这种越想越绕的感觉了——C#里的按值传递因为引用类型的存在,确实很容易让人混淆。咱们一步一步拆解你的问题,把底层逻辑理清楚:
先明确核心规则:C#默认都是按值传递
不管你传的是int这种值类型,还是Book这种引用类型,默认都是按值传递。这里的关键是:
- 传值类型时,传递的是数据本身的副本;
- 传引用类型时,传递的是对象引用的副本——划重点,是「引用的副本」,不是引用本身!
第一个例子:为什么修改属性会影响原对象?
看你这段代码:
Book book1 = new Book("Fight club"); ChangeBookName(book1, "The Wolfman"); void ChangeBookName(Book book, string name){ book.Name = name; }
底层发生的事是这样的:
new Book("Fight club")在托管堆上创建了一个Book实例,然后返回一个指向这个堆内存地址的引用,把这个引用存在栈上的book1变量里。- 调用
ChangeBookName时,把book1里的那个地址值复制了一份,传给方法里的book参数(这个参数也是栈上的局部变量)。 - 这时候
book和book1指向的是堆里同一个Book对象,所以book.Name = name本质是修改了堆里那个共同对象的属性,外部的book1自然能看到这个变化。
第二个例子:为什么new之后原对象没变化?
再看这段代码:
Book book1 = new Book("Fight club"); ChangeBookName(book1, "The Wolfman"); void ChangeBookName(Book book, string name){ book = new Book(name); }
前两步和上面一样:book参数拿到了book1引用的副本,指向同一个堆对象。但第三步不一样:
new Book(name)在堆里新建了一个完全独立的Book实例,得到一个新的内存地址;- 然后
book = new Book(name)是把方法内部的book参数里的地址值替换成了新对象的地址; - 但栈上的
book1变量里的地址根本没动,它还是指向最初的"Fight club"对象。所以外部的book1.Name自然还是原来的值。
解答你的几个疑问
1. 为什么new关键字不会覆盖原来的对象引用?
因为你传递的是引用的副本,不是原始引用本身。方法里修改的只是局部参数的引用,和外部的book1半毛钱关系都没有——就像你复制了一把家里的钥匙,然后把复制的钥匙改成开邻居家的门,你原来的钥匙还是开自己家的门。
2. new关键字是否创建了新的对象引用?
是的。new会在堆上分配新的内存空间创建对象,然后返回一个指向这个新空间的引用。这个新引用只会存在于方法内部的book参数里,除非你用ref/out把原始引用传进去。
3. 传递的原始值去向何方?
传递的原始引用副本还在方法的局部参数里,但当你给book赋值新引用后,原来的副本就被覆盖了——不过这无关紧要,因为它本来就是个副本,外部的原始引用book1一直没变。
4. 为什么新的Book实例没有覆盖旧实例?
因为两个实例在堆里是完全独立的内存块,一个是最初的"Fight club",一个是新的"The Wolfman"。book参数只是从指向旧块改成指向新块,旧块依然存在,book1还是指着它。
怎么让外部引用指向新对象?
如果想让book1也指向新创建的对象,你需要用ref关键字,把引用本身按引用传递:
Book book1 = new Book("Fight club"); ChangeBookName(ref book1, "The Wolfman"); void ChangeBookName(ref Book book, string name){ book = new Book(name); }
这时候传递的就不是引用的副本了,而是原始引用的「别名」,方法里修改book的引用,就等同于修改外部的book1引用。
内容的提问来源于stack exchange,提问作者user13192625
相关产品推荐
相关产品推荐

