【问题标题】:How parameter by reference/value works in C#引用/值参数在 C# 中的工作原理
【发布时间】:2021-03-09 17:03:26
【问题描述】:

我有这个示例代码:

    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,你能给我解释清楚吗?

谢谢

【问题讨论】:

    标签: c# parameter-passing pass-by-reference pass-by-value


    【解决方案1】:

    我会和你一起通过调试器一步一步来看看它是什么。

    我们看到我们进入了Foo,我们通过引用v传递了一个MyClass的实例(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的本地副本,并在内存地址@下创建了一个新对象987654337@ 在我们的 Main 方法中没有引用。

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

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

    在底层,它们的实现方式相同!

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

    【讨论】:

    • “我们通过引用传递了 v” - 我猜 v 更多的是引用而不是实例,这是按值传递的(这就是问题有意义的原因),但是类的实例v 引用的引用通过引用(即v)传递,正如您在括号中正确总结的那样
    • @E.Shcherbo 你是对的。 v 是一个引用——它是一个指向类实例的值,但可以看出你的句子有多复杂!我花了好一分钟才理解它!
    • 我同意并且很抱歉我不是想说你写错了什么,我只是写了一个曾经帮助我完全理解通过引用和价值传递的结论,所以它可能也会有用给别人:)
    【解决方案2】:

    因为当你调用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 也会重新分配to m: 输出 3:

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

    【讨论】:

      【解决方案3】:

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

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

      public static void Foo(ref MyClass v)

      然后这样称呼它;

      Foo(ref m)

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

      【讨论】:

        【解决方案4】:

        当您将引用传递给方法时,该引用将复制到堆栈上的另一个变量中。这两个变量(引用)可能仍然引用同一个对象,但变量本身是不同的。和这个是一样的:

        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 以便它引用一个全新的对象.当您传递对方法的引用时也会发生同样的情况,它只是被复制到另一个变量中。

        主要思想你可以从中得到实例类默认通过引用传递(因为你实际上是通过引用) ,但引用本身是按值传递的,这意味着它们被复制到另一个变量中。

        【讨论】:

          【解决方案5】:

          在调用 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();
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 2011-11-17
            • 2013-01-07
            • 2012-09-02
            • 1970-01-01
            • 2011-08-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-12-02
            相关资源
            最近更新 更多