【问题标题】:Why a String object has to be passed by reference?为什么必须通过引用传递 String 对象?
【发布时间】:2015-02-04 18:41:14
【问题描述】:

据我所知,在 C# 中,方法调用期间参数的传递是按值传递的。但是当您使用对象作为参数时,您传递的是对象本身的引用。这意味着,如果您访问(和修改)对象内的字段值,则在方法调用完成后也会看到更新。

因此,如果我在方法中修改 String 的值,则应该在方法调用终止时对其进行修改(因为 String 在 C# 中是一个对象)。但这不是真的,事实上如果我写:

public static void main (String []args){
  String s= "hello";
  method(s);
  System.Console.Writeline(s);
}

public void method (String s)
{s = "world";}

它将打印“hello”,而不是“world”。打印“world”的唯一方法是在方法签名中添加关键字ref并调用。

为什么会这样?我的答案(我希望您确认或更正)是在 C# 中 String 对象是不可变的,所以如果我让 s="world" 实际上编译器正在创建一个新的字符串对象,但是对对象 String s 的引用不会改变(因为段落是按值计算的)。

事实上,如果我在method()调用之前和之后打印s.getHashCode(),这两个值是不同的。

你觉得我的解释怎么样?

【问题讨论】:

  • 该方法创建了一个指向内存中相同空间的新变量。您正在更改 method 中的变量以指向其他内容,而不是 main 中的变量。

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


【解决方案1】:

因此,如果我在方法中修改 String 的值,则应该在方法调用终止时对其进行修改(因为 String 在 C# 中是一个对象)。但这不是真的,事实上如果我写:

您没有修改 String 对象。您正在修改 参数 以引用不同的 String。这只是更改了一个局部变量,调用者看不到

字符串引用是按值传递的,就像平常一样。您需要区分更改参数的值和修改参数值所引用的对象。您将看到与您自己的类完全相同的行为,即使它是可变的:

class Person
{
    public string Name { get; set; }
}

class Test
{
    static void Main()
    {
        var p = new Person { Name = "Tom" };
        Method(p);
        Console.WriteLine(p.Name);
    }

    static void Method(Person parameter)
    {
        parameter = new Person { Name = "Robin" };
    }
}

现在,如果在 Method 中您对 object 进行了更改,例如

static void Method(Person parameter)
{
    parameter.Name = "Robin";
}

... 然后 你会看到输出的变化。但这不是修改参数。字符串的不变性相关的唯一原因是,当参数是字符串时,这意味着上面的 Method 的第二个版本没有等效项(在安全代码中)。

更多详情请见my article on parameter passing

【讨论】:

  • 这处理字符串的不变性。不是参数传递。
  • 所以(只是为了检查我是否理解正确)你告诉我,当我调用方法时,参数指向作为参数传递给方法的 String 对象。但是,当我在方法内部进行赋值时,参数将指向另一个 String 对象(因为,就像我说的,String 是不可变的),其值为“world”,而原始的“hello”字符串在所有,它根本不再指向了!我是对的还是我还是不明白?
  • @MattClark 这两个问题都与问题有关。
  • @justHelloWorld:我是说mains 的值被复制到method 中的s,因为这就是按值传递的含义。然后在方法中为s 分配一个新值,这不会更改mains 的值,因为这就是按值传递的含义。您需要了解s 的值不是 一个字符串,它是一个参考......其余的很简单。 (想象一下,您有 int 值 - 您不会期望 method 中的 s = 10 更改 mains 的值,对吗?)
【解决方案2】:

因此,如果我在方法中修改 String 的值,则应该在方法调用终止时对其进行修改(因为 String 在 C# 中是一个对象)。

是的,这是 100% 正确的。如果您能够修改实际的 string 实例,那么调用者将能够观察到这种变化。

当然,因为string 是不可变的你无法修改字符串实例。这就是它不可变的含义。

由于您实际上并未修改string 实例,因此调用者无需实际观察到任何修改。

您在代码中所做的不是修改字符串实例,而是修改持有对字符串的引用的变量。该变量与 main 方法中的局部变量完全不同。修改一个不会影响另一个(除非您使用ref)。

你所拥有的是完全不同的变量,每个变量都引用一个字符串。如果您更改两者都引用的字符串,则可以从任一位置观察到该更改。更改其中一个变量以引用新的东西不是另一个变量可以观察到的。

【讨论】:

  • 我实际上想知道如果用 C++ 表示其中一些是否更容易解释。并不是说我比我必须更喜欢使用该语言工作,但它至少对每个声明所指的内容更清楚一点,非常具体。
  • @Katana314:仅当您与已经了解 C++ 的人交谈时。如果从头开始,它不应该更容易或更难。同一事物的语法因语言而异。不管你解释 C++ 中的语法是什么意思,用同样的方式解释 C# 中的等价语法应该不会更难。
  • @newacct 嗯,区别在于在 C++ 中,指针和值类型之间的区别必须用符号清楚地描述。这有点像显示一个年轻的数学学生 3 个点,然后再显示 3 个点,而不是“3 + 3”;更多工作,但仅显示为增量学习步骤。我记得很早以前,我花了一些时间自学来了解事物在 Java 中的“引用”以及它们的“值”。
【解决方案3】:

它与字符串的不变性无关。这是引用类型变量的默认行为。当您分配新的引用时,您正在更改变量的引用值。引用同一位置的其他变量不受该分配的影响。在您的情况下,当您不使用 ref 时,s 的参考值将被复制到 parameter 中,如下所示:

string s = "hello"
string parameter = s;

之后,如果您为parameter 分配新引用,它只会更改parameter 指向的位置,不会影响s。当您使用 ref 时,没有复制。在这种情况下,您实际上传递的是 reference 而不是引用的副本。这就是为什么您可以更改 where @ 987654329@指向。 再次它适用于所有引用类型,而不仅仅是字符串。

【讨论】:

  • 我现在完全明白你的回答了,但我还是认为在某种程度上字符串不变性属性仍然以某种方式涉及,事实上:字符串是不可变的——字符串对象的内容不能在创建对象后更改,尽管语法使它看起来好像您可以执行此操作。例如,当您编写此代码时,编译器实际上会创建一个新的字符串对象来保存新的字符序列,并将该新对象分配给 b。然后字符串“h”就可以进行垃圾回收了。所以我认为以某种方式涉及不变性
  • @justHelloWorld 我在回答中解释了涉及不变性的程度,也就是说切线。也就是说,string 的语法并没有表明字符串是可变的。它们的语法非常指示不可变对象(这是有道理的,因为它是不可变的)。
  • @justHelloWorld:不涉及不变性。无论可变性或不变性如何,相同的语法具有相同的语义。事实上,语言不知道类型是可变的还是不可变的。
猜你喜欢
  • 2015-06-18
  • 1970-01-01
  • 2014-05-27
  • 1970-01-01
  • 2021-05-02
  • 2016-12-09
  • 1970-01-01
  • 2011-10-16
  • 2011-09-04
相关资源
最近更新 更多