C#字符串引用类型?

157 c# string types reference

我知道C#中的"string"是一个引用类型.这是在MSDN上.但是,此代码不能正常工作:

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}
Run Code Online (Sandbox Code Playgroud)

输出应该在"传递之前""传递之后",因为我将字符串作为参数传递并且它是引用类型,第二个输出语句应该识别在TestI方法中更改了文本.但是,我在"通过之前""在通过之前"得到它似乎是通过值传递而不是通过ref.我知道字符串是不可变的,但我不知道这将如何解释这里发生的事情.我错过了什么?谢谢.

Jon*_*eet 204

对字符串的引用按值传递.通过值传递引用和通过引用传递对象之间存在很大差异.不幸的是,在两种情况下都使用了"引用"这个词.

如果你这样做把这个字符串参考参考,因为你希望它会工作:

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要区分对引用所引用的对象进行更改,以及对变量(例如参数)进行更改以使其引用其他对象.我们无法对字符串进行更改,因为字符串是不可变的,但我们可以使用以下代码StringBuilder来演示它:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅有关参数传递的文章.

  • 同意,只是想明确表示使用ref修饰符也适用于非引用类型,即两者都是非常独立的概念. (2认同)
  • @Jon Skeet喜欢你文章中的旁注.你应该"引用"那个作为你的答案 (2认同)

Mar*_*rov 32

如果我们必须回答这个问题:String是一个引用类型,它的行为就像一个引用.我们传递一个包含引用的参数,而不是实际的字符串.问题在于功能:

public static void TestI(string test)
{
    test = "after passing";
}
Run Code Online (Sandbox Code Playgroud)

该参数test保存对字符串的引用,但它是一个副本.我们有两个指向字符串的变量.因为任何带字符串的操作实际上都会创建一个新对象,所以我们将本地副本指向新字符串.但原始test变量没有改变.

建议的解决方案放在ref函数声明和调用工作中,因为我们不会传递test变量的值,而只会传递对它的引用.因此,函数内部的任何更改都将反映原始变量.

我想在最后重复:String是一个引用类型,但由于它是不可变的,所以该行test = "after passing";实际上创建了一个新对象,并且该变量的副本test被更改为指向新字符串.


Der*_*k W 24

正如其他人所说,String.NET中的类型是不可变的,它的引用是按值传递的.

在原始代码中,只要此行执行:

test = "after passing";
Run Code Online (Sandbox Code Playgroud)

然后test不再是指原始对象.我们创建了一个 String对象,并指定test在托管堆上引用该对象.

我觉得很多人都被绊倒了,因为没有可见的正式构造提醒他们.在这种情况下,它发生在幕后,因为该String类型在构造方式方面具有语言支持.

因此,这就是为什么在方法test范围之外看不到更改的原因TestI(string)- 我们已经通过值传递了引用,现在该值已经改变了!但是如果String引用是通过引用传递的,那么当引用更改时,我们将看到它超出了TestI(string)方法的范围.

无论是REF需要在这种情况下关键字.我觉得out关键字可能稍微适合这种特殊情况.

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}
Run Code Online (Sandbox Code Playgroud)


Mes*_*ssi 18

一张照片胜过千言万语”。

我这里有一个简单的例子,它类似于你的情况。

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc
Run Code Online (Sandbox Code Playgroud)

这是发生的事情:

在此处输入图片说明

  • 第1行和第2行:s1s2变量引用同一个"abc"字符串对象。
  • 第 3 行:因为字符串是不可变的,所以"abc"字符串对象不会修改自己(到"def"),而是"def"创建一个新的字符串对象,然后s1引用它。
  • 第 4 行:s2仍然引用"abc"字符串对象,这就是输出。


egl*_*ius 9

实际上对于任何对象来说都是相同的,即作为引用类型并且通过引用传递是c#中的两个不同的东西.

这可行,但无论类型如何都适用:

public static void TestI(ref string test)
Run Code Online (Sandbox Code Playgroud)

关于字符串作为引用类型,它也是一个特殊的类型.它的设计是不可变的,因此它的所有方法都不会修改实例(它们返回一个新实例).它还有一些额外的东西用于提高性能.


Bry*_*yan 6

这是考虑值类型,传值,引用类型和传递引用之间差异的好方法:

变量是一个容器.

值类型变量包含实例.reference-type变量包含指向存储在别处的实例的指针.

修改value-type变量会改变它包含的实例.修改引用类型变量会改变它指向的实例.

单独的引用类型变量可以指向同一个实例.因此,可以通过指向它的任何变量来突变相同的实例.

传值参数是一个带有新内容副本的新容器.传递引用参数是具有原始内容的原始容器.

当value-type参数按值传递时:重新分配参数的内容对范围外没有影响,因为容器是唯一的.修改参数对范围外没有影响,因为实例是一个独立的副本.

当reference-type参数按值传递时:重新分配参数的内容对范围外没有影响,因为容器是唯一的.修改参数的内容会影响外部作用域,因为复制的指针指向共享实例.

当任何参数通过引用传递时:重新分配参数的内容会影响外部范围,因为容器是共享的.修改参数的内容会影响外部范围,因为内容是共享的.

结论:

字符串变量是引用类型变量.因此,它包含指向存储在别处的实例的指针.当按值传递时,会复制其指针,因此修改字符串参数应该会影响共享实例.但是,字符串实例没有可变属性,因此无论如何都不能修改字符串参数.当通过引用传递时,指针的容器是共享的,因此重新分配仍将影响外部范围.


Nas*_*efi 6

为了好奇并完成对话: 是的,String 是一种引用类型

unsafe
{
     string a = "Test";
     string b = a;
     fixed (char* p = a)
     {
          p[0] = 'B';
     }
     Console.WriteLine(a); // output: "Best"
     Console.WriteLine(b); // output: "Best"
}
Run Code Online (Sandbox Code Playgroud)

但请注意,此更改仅适用于不安全的块!因为字符串是不可变的 (来自 MSDN):

字符串对象的内容在创建对象后就无法更改,尽管语法使您看起来好像可以执行此操作。例如,当您编写此代码时,编译器实际上会创建一个新的字符串对象来保存新的字符序列,并将该新对象分配给 b。然后字符串“h”就可以进行垃圾回收。

string b = "h";  
b += "ello";  
Run Code Online (Sandbox Code Playgroud)

请记住:

尽管字符串是引用类型,但定义相等运算符 (==!=) 是为了比较字符串对象的值,而不是引用。


Bor*_*ode 5

上面的答案很有帮助,我只想添加一个示例,我认为它可以清楚地说明当我们传递没有 ref 关键字的参数时会发生什么,即使该参数是引用类型:

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }
Run Code Online (Sandbox Code Playgroud)