参数真的按值传递吗?

Is really argument passing by value?

我正在读 John Skeet 的书 "C# in Depth"。他在第 74 页上说,每个人都假设参数通过引用传递给函数,同时它通过值传递,并且作为示例,他展示了这段代码,必须证明调用代码中的 StringBuilder 没有改变。同时在我们的函数内部 StringBuilder 实例发生了变化。

private static void SayHello(StringBuilder s)
 {
   s.AppendLine("Hello");
 }

但我的实验表明 StringBuilder 对象发生了变化 - 我们将在控制台中看到 "Hello"。这里有什么问题?或者我对这个例子的理解有什么问题?

       private static void Main(string[] args)
        {
           var s  = new StringBuilder();
            Console.WriteLine(s.ToString());
            SayHello(s);
            Console.WriteLine(s.ToString());
            Console.ReadLine();
        }

        private static void SayHello(StringBuilder s)
        {
            s.AppendLine("Hello");
        }

看完页面,他说的是这个

Now remember that the value of a reference type variable is the reference, not the object itself. You can change the contents of the object that a parameter refers to without the parameter itself being passed by reference.

    public static void Main()
    {
        var s = new StringBuilder();
        Console.WriteLine(s.ToString());
        SayHello(s);
        Console.WriteLine(s.ToString());
        Console.ReadLine();
    }

    private static void SayHello(StringBuilder s)
    {
        s.AppendLine("Hello");
        s = null;
    }

    //output will be Hello

When this method is called, the parameter value (a reference to a StringBuilder) is passed by value. If I were to change the value of the builder variable within the method—for example, with the statement builder = null;—that change wouldn’t be seen by the caller, contrary to the myth.

他几乎解释说你不能破坏 SayHello 中的对象 StringBuilder,你可以只改变这个对象的内容。我也不知道。

编辑:关于您的评论

当你在 SayHello 中传递 s 时,你正在创建新的引用,并且 Main 方法和 SayHello 中的引用都指向同一个对象。但它们像 2 个不同的引用一样存在。

如果你写在

private static void SayHello(StringBuilder s)
{
    s.AppendLine("Hello");
    s = new StringBuilder();

}

SayHello 中 s 的引用将指向另一个对象,但这不会更改 Main 方法中的任何内容,因为该引用指向您编写 Hello 的前一个对象。所以再次输出 Hello.

如果你想传递准确的引用,你应该使用 ref

所以如果你写

private static void SayHello(ref StringBuilder s)
{
     s.AppendLine("Hello");
     s = null;
     //you will receive an exception in Main method, because now the value in the reference is null.
}

考虑以下类比:

  1. 您购买了一套新公寓 (new StringBuilder()) 并获得了一把钥匙 (s),您可以使用它。
  2. 你雇了一个清洁工 (s.AppendLine),你给了他你钥匙的副本,这样他就可以进入你的公寓并打扫它。
  3. 清洁工使用您的钥匙副本,打扫您的公寓,并且因为他喜欢,所以重新编程钥匙以打开其他公寓。
  4. 你完成日常工作回家。你的钥匙打开你公寓的门就好了,你发现它很干净。

这基本上就是按值传递引用类型时发生的情况。

现在考虑以下问题:

  1. 您购买了一套新公寓 (new StringBuilder()) 并获得了一把钥匙 (s),您可以使用它。
  2. 你雇了一个清洁工 (s.AppendLine),你把你的钥匙给了他,这样他就可以打扫你的公寓了。
  3. 清洁工打扫您的房间,因为他喜欢,所以重新编程您给他的钥匙以打开其他公寓。他把你的钥匙放在门垫下面了。
  4. 你完成日常工作回家。您尝试使用您的钥匙,但门打不开。通过window你可以看到你的公寓很干净。

这就是通过引用传递引用类型时发生的情况。