C#字符串引用类型?

时间:2009-07-08 06:44:14

标签: c# string reference types

我知道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";
    }
}

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

12 个答案:

答案 0 :(得分:198)

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

如果您 通过引用传递字符串引用,它将按预期工作:

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";
    }
}

现在,您需要区分对引用所引用的对象进行更改,以及对变量(例如参数)进行更改以使其引用其他对象。我们无法对字符串进行更改,因为字符串是不可变的,但我们可以使用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");
    }
}

有关详细信息,请参阅my article on parameter passing

答案 1 :(得分:30)

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

public static void TestI(string test)
{
    test = "after passing";
}

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

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

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

答案 2 :(得分:23)

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

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

test = "after passing";

然后test不再引用到原始对象。我们创建了一个 new String对象,并指定test来引用托管堆上的该对象。

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

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

在这种情况下,需要 ref out 关键字。我觉得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";
    }
}

答案 3 :(得分:9)

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

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

public static void TestI(ref string test)

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

答案 4 :(得分:6)

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

变量是容器。

值类型变量包含一个实例。 引用类型变量包含指向存储在别处的实例的指针。

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

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

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

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

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

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

总结:

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

答案 5 :(得分:4)

以上答案很有帮助,我想添加一个示例,我认为清楚地演示了在没有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.
        }

答案 6 :(得分:2)

出于好奇的想法并完成对话: 是的,字符串是引用类型

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

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

  

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

string b = "h";  
b += "ello";  

请记住:

  

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

答案 7 :(得分:2)

一张图片值得一千个字”。

我在这里有一个简单的示例,它与您的情况类似。

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

这是发生了什么

enter image description here

  • 第1行和第2行:s1s2变量引用相同的"abc"字符串对象。
  • 第3行:因为strings are immutable,所以"abc"字符串对象不会自行修改(更改为"def"),而是创建了一个新的"def"字符串对象,并且然后s1对其进行引用。
  • 第4行:s2仍引用"abc"字符串对象,因此是输出。

答案 8 :(得分:0)

我相信您的代码类似于以下内容,并且您不应该因为与此不相同的原因而预期值已更改:

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }

答案 9 :(得分:0)

我认为传递字符串有3种选择

#1如果使用ref,则需要指定一个函数来传递此对象

 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";
        }
    }

#2С创建一个我们传递值的类

  public static void Main()
        {
            DATA.Str = "before passing";
            Console.WriteLine(DATA.Str);
            TestI("after passing");
            Console.WriteLine(DATA.Str);
        }
        public class DATA
        {
            public static string Str { get; set; } = "";
        }
        public static void TestI(string  text)
        {
            DATA.Str = text;
        }

#3通过线程使用数据交换

    class Test
    {
        public static void Main()
        {
            Thread.SetData(Thread.GetNamedDataSlot("memory"), "before passing");
            var n = Thread.GetData(Thread.GetNamedDataSlot("memory"));
            Console.WriteLine(n);
            TestI("after passing");
            n = Thread.GetData(Thread.GetNamedDataSlot("memory"));
            Console.WriteLine(n);
        }

        public static void TestI(string  text)
        {
            Thread.SetData(Thread.GetNamedDataSlot("memory"), text);
        }
    }

答案 10 :(得分:0)

另一种绕过字符串行为的方法。仅使用一个元素的字符串数组并操作此元素。

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

    public static void TestI(ref string[] test)
    {
        test[0] = "after passing";
    }
}

答案 11 :(得分:-1)

尝试:


public static void TestI(ref string test)
    {
        test = "after passing";
    }