【问题标题】:Passing string ByVal in VB.NET AND C#在 VB.NET 和 C# 中传递字符串 ByVal
【发布时间】:2011-07-07 00:02:17
【问题描述】:

所以字符串是引用类型对吧?我的理解是即使将字符串 ByVal 传递给方法时,也会传递对堆中字符串的引用。

呜呜呜……

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

我错过了什么?引擎盖下发生了什么?我敢打赌价值会改变....

【问题讨论】:

  • 我推荐阅读这篇文章:yoda.arachsys.com/csharp/parameters.html 以及重复问题的答案。
  • 哇,我一点也不明白。 myTestValue 怎么可能改变呢?不是在修改吗?你认为你到底是如何修改它的?
  • @Stan:人们非常(非常)经常混淆按引用传递和按值传递引用类型

标签: c# vb.net reference-type byref byval


【解决方案1】:

默认情况下,包括引用类型在内的所有类型都按值传递,如您的示例所示,这意味着传递了引用的副本。因此,无论如何,当您按值传递对象时,重新分配这样的对象将无效。您只是在更改参考指向的副本。您必须通过引用显式传递才能实现您想要做的事情。

只有当你修改一个按值传递的对象时,才能在方法外看到效果。当然字符串是不可变的,所以这里并不适用。

【讨论】:

  • 字符串是引用并存储在堆上,与问题无关。字符串的存储方式并没有什么特别之处,只是它们是唯一可以是文字的引用类型。
【解决方案2】:

引用类型在 .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。在这种情况下,字符串与任何其他类型没有什么不同。
  • @Joel Coehoorn:谢谢。我不指望像你这样的人会阅读全文。希望它足够简单,对提出这个问题的人有用。随意编辑您认为不必要的冗长的任何部分。
【解决方案3】:
  1. 当您将字符串传递给方法时,将获取引用的副本。因此,Value 是一个全新的变量,恰好仍然引用内存中的同一个字符串。
  2. "test" 字符串文字也被创建为真正的引用类型对象。它不仅仅是源代码中的一个值。
  3. 当您将"test" 分配给Value 时,您的Value 变量的引用将更新为引用"test" 而不是原始字符串。由于这个引用只是一个副本(正如我们在步骤 1 中看到的),函数外部的 myTestValue 变量保持不变,仍然引用原始字符串。

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

【讨论】:

    【解决方案4】:

    除非您另有说明,否则一切都是按值传递的。传递字符串时,实际上是按值传递引用。

    对于字符串,这并没有太大区别,因为字符串是不可变的。这意味着您永远无法修改收到的字符串。但是,对于其他类,您可以修改按值传递的对象(除非像 String 一样,它是不可变的)。您不能做的以及通过引用传递允许您做的是修改您正在传递的变量

    例子:

    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 中,当您通过引用传递时,它并不总是清楚地表明,当某些方法意外地开始与您的变量混在一起时,它可能会导致很多问题。

    【讨论】:

      【解决方案5】:

      您传递的是副本而不是实际参考。

      从微软阅读这篇文章

      http://msdn.microsoft.com/en-us/library/s6938f28.aspx

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-10-08
        • 2013-06-27
        • 2013-06-25
        • 2018-06-13
        • 2012-01-21
        • 2017-04-06
        • 1970-01-01
        相关资源
        最近更新 更多