C#方法调用后输出与预期不符?Method3/Method4传参原理详解
C#传参机制与输出差异原因解释
核心前置规则
C#中所有参数默认采用值传递逻辑,传递的是参数存储内容的副本,根据参数类型不同,副本的内容有区别:
- 值类型(如
int、bool等):变量本身直接存储实例数据,值传递时副本就是数据本身,方法内修改副本完全不影响外部原变量 - 引用类型(如自定义类
A、string等):变量本身存储的是堆上对象实例的内存引用地址,值传递时副本是这个引用地址,方法内的参数副本和外部原变量初始指向堆上同一个对象实例 string作为特殊的不可变引用类型,任何对字符串的“修改”本质都是创建新字符串实例、改变变量指向的引用地址,和普通引用类型修改内部属性的逻辑不同
相关测试代码
public class A { public int A1 { get; set; } public int A2 { get; set; } } static void Main(string[] args) { int i = 0; Method1(i); Console.WriteLine($"i={i}"); var str = "This is a string"; Method2(str); Console.WriteLine(str); var a = new A() { A1 = 5, A2 = 6 }; Method3(a); Console.WriteLine($"a.A1={a.A1}, a.A2={a.A2}"); Method4(a); Console.WriteLine($"a.A1={a.A1}, a.A2={a.A2}"); Method5(ref a); Console.WriteLine($"a.A1={a.A1}, a.A2={a.A2}"); } private static void Method1(int i) { i = 5; } private static void Method2(string str) { str = "This is a new string"; } private static void Method3(A a) { a.A1 = 6; } private static void Method4(A a) { a = new A() { A1 = 10, A2 = 11 }; } private static void Method5(ref A a) { a = new A() { A1 = 10, A2 = 11 }; }
Method3 工作机制
- 传参阶段:方法内的参数
a是Main方法中a变量存储的引用地址的副本,二者初始指向堆上同一个A类实例(初始值A1=5、A2=6) - 执行阶段:
a.A1 = 6是通过持有的引用地址找到堆上的实际对象,直接修改该实例的属性值,这个修改是持久作用在原实例上的 - 执行结束:方法内参数副本被回收,外部Main中的
a依然指向这个被修改过属性的实例,因此第一次打印类实例时输出a.A1=6, a.A2=6
Method4 工作机制
- 传参阶段:方法内的参数
a同样是Main中a存储的引用地址的副本,初始指向Method3修改过的那个A实例(A1=6、A2=6) - 执行阶段:
a = new A() {A1=10, A2=11}是将方法内参数副本本身存储的引用地址,替换为新创建的A实例的地址。这个操作仅修改方法作用域内的局部参数,既没有改动外部Main中a存储的引用地址,也没有修改原来堆上的旧实例 - 执行结束:方法内的参数副本出作用域被回收,外部Main中的
a依然指向之前的旧实例,因此第二次打印类实例时输出还是a.A1=6, a.A2=6
输出差异的核心原因
预期输出和实际输出不符,来自两个认知偏差:
- 误认为引用类型默认值传递时,方法内无法修改原对象的状态:实际上只要没有给参数变量重新赋值新实例,通过引用副本访问、修改原实例的内部属性,会直接作用在原对象上,因此Method3会把原实例的A1从5改成6
- 忽略了Method3已经修改了原实例的属性值:Method4确实不会改变外部
a的指向,但原实例的A1已经被Method3改成6,不会回到初始值5,因此不会出现预期中a.A1=5的输出
补充:Method5的ref传参逻辑
Method5使用ref关键字修饰参数时,不再传递引用地址的副本,而是直接传递外部变量本身的存储位置,方法内的a和Main中的a是同一个存储单元。此时给a赋值新实例,会直接修改外部变量的指向,因此最后打印时会输出新实例的属性a.A1=10, a.A2=11,这部分和预期一致。
内容的提问来源于stack exchange,提问作者Dan6073
相关产品推荐
相关产品推荐

