reference/value 的参数如何在 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以便它引用一个全新的对象。当您将引用传递给该方法时,也会发生同样的情况,它只是被复制到另一个变量中。

你可以从中得到的主要思想是类的实例传递给reference 默认情况下(因为你实际上传递了 reference),但是 references 本身是通过 by value 传递的,这意味着它们是复制到另一个变量中。

在调用 v = new MyClass(); 时,在 Foo 内部,引用不再指向传递的对象,而是指向创建的新对象。

这不会影响调用者,因为新对象不是在为旧对象分配的内存中创建的,而是变量 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) 时,vm 是对同一对象的单独引用。

重新分配给 v 不会重新分配给 m

将此与以下对比:

public static void Foo(ref MyClass v)
{
    v.Value = 2;
    v = new MyClass();
    v.Value = 3;
}

通过使用ref,如果你现在调用Foo(m)vm成为相同的引用到同一个对象,所以重新分配给 v 也会重新分配给 m:使输出 3:

static void Main(string[] args)
{
    var m = new MyClass();
    m.Value = 1;
    Foo(m);
    Console.Write(m.Value);
    Console.ReadLine();
}

我将通过调试器一步一步地与您一起完成,我们将看到它是什么 2.

我们看到我们进入了 Foo 并通过引用传递了 MyClass 的实例 v(class C# 中的实例默认通过引用传递)

在记忆中,我们会看到这样的东西:

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*

当我们退出方法时,我们并没有替换内存地址0x01的内容,而是为函数的作用域创建了一个v的本地副本,并且我们创建了一个新的内存地址 0x02 下的对象,我们的 Main 方法中没有引用它。

我们的主要方法是使用来自地址 0x01 的实例,而不是我们在 Foo 方法中创建的新 0x02

为了确保我们传递正确的对象,我们需要告诉 C# 我们想要使用 ref“编辑”输出或者我们想要使用 out“覆盖”输出。

在后台,它们的实现方式相同!

我们没有将 0x01 传递给我们的 Foo 方法,而是传递了 0x03!它有一个指向 0x01 下的 class 的指针。因此,当我们在使用 refout 时分配 v = new MyClass() 时,实际上我们修改了 0x03 的值,然后在我们的 Main 方法中将其提取并“替换”包含正确的值!

正在 Foo 函数内初始化 v 对象会创建一个新的 MyClass 实例,但它的引用未设置为 Main 函数内的 m 对象。因为对象的引用是按值传递的。

如果你想在 Foo 中引用它,你应该像这样使用 ref

public static void Foo(ref MyClass v)

然后这样称呼它;

Foo(ref m)

如果传递的参数必须由方法初始化,您也可以使用out代替ref