【问题标题】:C# string reference type?C#字符串引用类型?
【发布时间】:2010-11-08 22:44:58
【问题描述】:

我知道 C# 中的“字符串”是一种引用类型。这是在 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 方法中的文本发生了变化。但是,我得到“通过之前”“通过之前”,这使它看起来是按值而不是通过引用传递的。我知道字符串是不可变的,但我不明白这将如何解释这里发生的事情。我错过了什么?谢谢。

【问题讨论】:

标签: c# string reference types


【解决方案1】:

对字符串的引用是按值传递的。按值传递引用和按引用传递对象之间有很大的区别。不幸的是,在这两种情况下都使用了“参考”这个词。

如果你传递字符串引用by引用,它将按你的预期工作:

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

【讨论】:

  • 同意,只是想明确一点,使用 ref 修饰符也适用于非引用类型,即两者是完全不同的概念。
  • @Jon Skeet 喜欢您文章中的旁注。你应该把 referenced 作为你的答案
【解决方案2】:

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

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

    test = "after passing";
    

    那么test 不再引用原始对象。我们创建了一个new String 对象并分配test 以引用托管堆上的该对象。

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

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

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

    【讨论】:

    • ref = 在函数外部初始化,out = 在函数内部初始化,或者换句话说; ref 是双向的,out 是 out-only。所以肯定应该使用 ref。
    • @PaulZahra: out 需要在编译代码的方法中赋值。 ref 没有这个要求。 out 参数也在方法之外初始化 - 这个答案中的代码是一个反例。
    • 应该澄清 - out 参数可以在方法之外初始化,但不是必须的。在这种情况下,我们要初始化 out 参数以说明 .NET 中 string 类型的本质。
    【解决方案4】:

    一张图胜过千言万语”。

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

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

    事情是这样的:

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

    【讨论】:

    • 目前为止最好的解决方案,只是快速轻松地理解它的最简单方法。干得好。
    • 是""一张图抵千言""
    【解决方案5】:

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

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

    public static void TestI(ref string test)
    

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

    【讨论】:

      【解决方案6】:

      这是思考值类型、按值传递、引用类型和按引用传递之间区别的好方法:

      变量是一个容器。

      值类型变量包含一个实例。 引用类型变量包含指向存储在其他地方的实例的指针。

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

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

      按值传递的参数是具有新内容副本的新容器。 通过引用传递的参数是具有其原始内容的原始容器。

      当值类型参数按值传递时: 重新分配参数的内容在范围之外没有影响,因为容器是唯一的。 修改参数在作用域之外没有影响,因为实例是一个独立的副本。

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

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

      总结:

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

        • 这个解释最适合我。所以基本上我们通过值传递所有东西,尽管变量本身是值或引用类型,除非我们使用 ref 关键字(或 out)。这在我们的日常编码中并不突出,因为我们通常不会将对象设置为 null 或传入它们的方法内的不同实例,而是设置它们的属性或调用它们的方法。在“字符串”的情况下,将其设置为一个新实例一直都在发生,但是新的更新是不可见的,这给未经训练的眼睛一个错误的解释。如有错误请指正。
        【解决方案8】:

        对于好奇的头脑并完成对话: 是的,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"
        }
        

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

        字符串对象的内容在对象被删除后不能更改 已创建,尽管语法使它看起来好像您可以执行此操作。 例如,当您编写此代码时,编译器实际上会创建一个 新的字符串对象来保存新的字符序列,并且那个新的 对象被分配给 b。字符串“h”然后有资格成为垃圾 收藏。

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

        请记住:

        虽然字符串是引用类型,但等式运算符(==!=) 被定义为比较字符串对象的值,而不是 参考文献。

        【讨论】:

          【解决方案9】:

          试试:

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

          【讨论】:

          • 您的答案应该不仅仅包含代码。它还应该包含对其工作原理的解释。
          【解决方案10】:

          我相信您的代码类似于以下代码,并且您不应该期望该值会因为同样的原因而发生变化:

           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
           }
          

          【讨论】:

            【解决方案11】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-04-10
              • 1970-01-01
              • 2010-11-07
              • 2022-07-06
              • 2013-08-27
              • 1970-01-01
              • 2022-01-16
              相关资源
              最近更新 更多