提问人:Wadjey 提问时间:11/27/2020 更新时间:11/27/2020 访问量:140
参数在 C 中按引用/值的工作原理#
How parameter by reference/value works in C#
问:
我有这个示例代码:
public class MyClass
{
public int Value { get; set; }
}
class Program
{
public static void Foo(MyClass v)
{
v.Value = 2;
v = new MyClass();
v.Value = 3;
}
static void Main(string[] args)
{
var m = new MyClass();
m.Value = 1;
Foo(m);
Console.Write(m.Value);
Console.ReadLine();
}
}
我想了解为什么输出是 2 而不是 3,你能给我一些明确的解释吗?
谢谢
答:
当您传递对方法的引用时,该引用将复制到堆栈上的另一个变量中。这两个变量(引用)可能仍引用同一对象,但变量本身是不同的。同如下:
var m = new MyClass();
m.Value = 1;
var s = m;
s.Value = 2; // m.Value is also 2
s = new MyClass();
s.Value = 3; // m.Value is still 2
你不会期望在这里等于,因为你有两个不同的变量,它们引用了堆上的同一个对象,但后来你改变了,它引用了一个全新的对象。当您传递对方法的引用时,也会发生同样的情况,它只是复制到另一个变量中。m.Value
3
s
你可以从中得到的主要思想是,默认情况下,类的实例是通过引用传递的(因为你实际上传递了引用),但引用本身是通过值传递的,这意味着它们被复制到另一个变量中。
在调用的那一刻,在 Foo 内部,引用不再指向传递的对象,而是指向已创建的新对象。v = new MyClass();
这不会影响调用方,因为新对象不是在为旧对象分配的内存中创建的,而是变量 v 现在指向新对象,而不是它曾经指向的对象。
这就是为什么 Foo 将值影响为 2,它是原始对象,但在重新分配 v 后,原始对象不受影响
public class MyClass
{
public int Value { get; set; }
}
class Program
{
public static void Foo(MyClass v)
{
v.Value = 2;
v = new MyClass(); // this will make v point to an other object
v.Value = 3;
}
static void Main(string[] args)
{
var m = new MyClass();
m.Value = 1;
Foo(m);
Console.Write(m.Value);
Console.ReadLine();
}
}
因为当你调用 时,和 是对同一对象的单独引用。Foo(m)
v
m
重新指派到不会重新指派到 。v
m
与此形成鲜明对比的是:
public static void Foo(ref MyClass v)
{
v.Value = 2;
v = new MyClass();
v.Value = 3;
}
通过使用 ,如果你现在调用 ,并成为对同一对象的相同引用,那么重新赋值 to 也会重新赋值到 : 使这个输出 3:ref
Foo(m)
v
m
v
m
static void Main(string[] args)
{
var m = new MyClass();
m.Value = 1;
Foo(m);
Console.Write(m.Value);
Console.ReadLine();
}
我将通过调试器逐步与您一起完成,我们将看到它是什么 2.
我们看到我们进入了 Foo,并通过引用传递了 MyClass 的实例(默认情况下,C# 中的类实例是通过引用传递的)v
在内存中,我们会看到这样的东西:
v = 0x01; //0x01 - is a simple representation of a pointer that we passed
v.Value = 1;
接下来,我们单步执行,我们看到我们更改了引用中的值 Value。
v = 0x01;
v.Value = 2; // our new value
然后我们分配给我们的 所以在内存中我们有new
v
v* = 0x01 // this is our "old" object
v*.Value = 2;
v = 0x02 // this is our "new" object
v.Value = 3;
如您所见,内存中有 2 个对象!新的和旧的都标有开始v
v*
当我们退出该方法时,我们没有替换内存地址的内容,而是为函数的作用域创建了一个本地副本,并在内存地址下创建了一个新对象,该对象在我们的 Main 方法中未引用。0x01
v
0x02
我们的主要方法是使用来自地址的实例,而不是我们在 Foo 方法中创建的新实例!0x01
0x02
为了确保我们传递正确的对象,我们需要告诉 C# 我们想要“编辑”输出 using ,或者我们想要 “覆盖” 输出 using .ref
out
在引擎盖下,它们的实现方式相同!
我们没有传递给我们的 Foo 方法,而是传递 !它有一个指向我们类的指针。因此,当我们赋值时使用 或 时,实际上,我们会修改其值,然后在 Main 方法中提取并“替换”以包含正确的值!0x01
0x03
0x01
v = new MyClass()
ref
out
0x03
评论
v
v
v
v
初始化函数内部的对象会创建一个新实例,但它的引用未设置为函数内部的对象。因为对象的引用是通过值传递的。v
Foo
MyClass
m
Main
如果你想在里面引用它,你应该这样使用;Foo
ref
public static void Foo(ref MyClass v)
并这样称呼它;
Foo(ref m)
您也可以使用“如果传递的参数必须由方法启动”,则可以代替。out
ref
评论