在VB.NET和C#中通过按值传递字符串

8

那么字符串是引用类型,对吗?我的理解是,即使您将字符串按值通过方法传递,也会传递到堆中的字符串的引用。

所以......

String myTestValue = "NotModified";
TestMethod(myTestValue);
System.Diagnostics.Debug.Write(myTestValue); /* myTestValue = "NotModified" WTF? */

private void TestMethod(String Value)
{
    Value = "test1";
}

或者

Dim myTestValue As String = "NotModified"
TestMethod(myTestValue)
Debug.Print(myTestValue) /* myTestValue = "NotModified" WTF? */

Private Sub TestMethod(ByVal Value As String)
    Value = "test1"
End Sub

我错过了什么?引擎下面发生了什么?我本以为这个值会改变...


我建议阅读这篇文章:http://www.yoda.arachsys.com/csharp/parameters.html 以及重复问题的答案。 - R. Martinho Fernandes
哇,我完全不理解这个。 myTestValue 怎么可能会改变呢?它没有被修改啊?你究竟是怎么修改它的? - Stan
1
@Stan:人们非常(非常)经常混淆按引用传递和通过值传递引用类型 - Adam Robinson
5个回答

8

在.NET中,引用类型是按“值传递引用”传递的。这意味着将不同的值分配给实际参数并不会实际更改原始值(除非您使用ByRef / ref)。但是,对传入的实际对象进行任何更改都将更改调用方法所引用的对象。例如,请考虑以下程序:

void Main()
{
    var a = new A{I=1};
    Console.WriteLine(a.I);
    DoSomething(a);
    Console.WriteLine(a.I);
    DoSomethingElse(a);
    Console.WriteLine(a.I);
}

public void DoSomething(A a)
{
    a = new A{I=2};
}

public void DoSomethingElse(A a)
{
    a.I = 2;
}

public class A
{
    public int I;
}

输出:

1
1
2
DoSomething方法将其a参数分配为具有不同值的值,但该参数只是调用方法中原始a位置的本地指针。更改指针的值对调用方法的a值没有任何影响。然而,DoSomethingElse实际上更改了引用对象上的一个值。
无论其他回答者说什么,string在这方面并不例外。所有对象都是这样行为的。 string与许多对象不同的地方在于它是不可变的:在字符串上没有任何可以调用以实际更改字符串的方法、属性或字段。一旦在.NET中创建了一个字符串,它就是只读的。
当你像这样做时:
var s = "hello";
s += " world";

编译器将其转换为以下内容:

// this is compiled into the assembly, and doesn't need to be set at runtime.
const string S1 = "hello"; 
const string S2 = " world"; // likewise
string s = S1;
s = new StringBuilder().Append(s).Append(S2).ToString();

这最后一行生成了一个新字符串,但S1和S2仍然存在。如果它们是内置到程序集中的常量字符串,它们将保留在那里。如果它们是动态创建的并且没有更多的引用,垃圾回收器可以取消引用它们以释放内存。但关键是要意识到S1实际上从未改变。指向它的变量只是改变了指向不同字符串的指针。

+1. 在这方面,字符串与任何其他类型没有区别。 - Adam Robinson
@Joel Coehoorn:谢谢。我不指望像您这样水平的人会读整篇文章。希望它简单到足以对提出此类问题的人有所帮助。如果您认为有不必要的冗长,可以自由编辑任何部分。 - StriplingWarrior

3
除非您另有说明,否则所有内容都是按值传递的。当您传递字符串时,实际上是通过引用按值传递的。
对于字符串来说,这并没有太大区别,因为字符串是不可变的。也就是说,您永远无法修改接收到的字符串。但对于其他类而言,您可以修改按值传递的对象(除非像字符串一样是不可变的)。您无法做到的是修改您正在传递的变量,这正是按引用传递允许您进行的操作。
例如:
Public Class Example
    Private Shared Sub ExampleByValue(ByVal arg as String)
        arg = "ByVal args can be modifiable, but can't be replaced."
    End Sub

    Private Shared Sub ExampleByRef(ByRef arg as String)
        arg = "ByRef args can be set to a whole other object, if you want."
    End Sub

    Public Shared Sub Main()
        Dim s as String = ""
        ExampleByValue(s)
        Console.WriteLine(s)  ''// This will print an empty line
        ExampleByRef(s)
        Console.WriteLine(s)  ''// This will print our lesson for today
    End Sub
End Class

现在,这应该非常节制地使用,因为按值传递是默认和预期的。特别是在VB中,它并不总是清楚地表明您正在通过引用传递,当某些方法意外地开始混淆您的变量时,它可能会导致许多问题。


2
默认情况下,所有类型(包括引用类型)都是按值传递的,就像你的例子一样,这意味着传递的是引用的副本。所以,无论如何,当你按值传递时重新分配一个对象都没有效果。你只是改变了引用副本所指向的位置。要实现你想要的效果,必须显式地按引用传递。
只有修改按值传递的对象时,才能在方法外看到效果。当然,字符串是不可变的,所以这里并不适用。

1
字符串是引用类型,存储在堆上,不过这与问题无关。字符串存储方式并没有什么特殊之处,只是它们是唯一可以作为字面量的引用类型。 - Adam Robinson

0

0
  1. 当您将字符串传递给方法时,引用的一个副本被获取。因此,Value 是一个全新的变量,只不过仍然引用相同的内存中的字符串。
  2. "test" 字符串字面量也被创建为一个真正的引用类型对象。它不仅仅是您源代码中的值。
  3. 当您将 "test" 赋值给 Value 时,Value 变量的引用被更新为引用 "test" 而非原始字符串。由于这个引用只是一个副本(如我们在步骤1中看到的),函数外部的 myTestValue 变量仍然保持不变,仍然引用原始字符串。

您可以通过测试具有您可以更新其属性的类型来更好地理解此内容。如果您只更改属性,则该更改在函数外部可见。如果您尝试替换整个对象(就像您正在做的这个字符串一样),则在函数外部不可见。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接