【问题标题】:Type 'string' as an argument in C# function在 C# 函数中键入“字符串”作为参数
【发布时间】:2011-09-04 04:51:56
【问题描述】:

C# 中的string 类型是引用类型,通过值传递引用类型参数会复制引用,因此我不需要使用ref 修饰符。但是,我需要使用ref 修饰符来修改输入string。这是为什么呢?

using System;

class TestIt
{
    static void Function(ref string input)
    {
        input = "modified";
    }

    static void Function2(int[] val) // Don't need ref for reference type
    {
        val[0] = 100;
    }

    static void Main()
    {
        string input = "original";
        Console.WriteLine(input);
        Function(ref input);      // Need ref to modify the input
        Console.WriteLine(input);

        int[] val = new int[10];
        val[0] = 1;
        Function2(val);
        Console.WriteLine(val[0]);
    }
}

【问题讨论】:

    标签: c# .net string ref


    【解决方案1】:

    你需要引用字符串参数的原因是,即使你传入一个对字符串对象的引用,给参数分配其他东西只会替换当前存储在参数变量中的引用 .也就是说,你改变了参数所指的东西,但原来的对象没有改变。

    当你引用参数时,你已经告诉函数该参数实际上是传入变量的别名,因此分配给它会有预期的效果。

    编辑:请注意,虽然 string 是不可变的引用类型,但在这里并不太相关。由于您只是试图分配一个新对象(在本例中是“修改”的字符串对象),因此您的方法不适用于 any 引用类型。例如,考虑对您的代码进行以下轻微修改:

    using System;
    
    class TestIt 
    {
        static void Function(ref string input)
        {
            input = "modified";
        }
    
        static void Function2(int[] val) // don't need ref for reference type
        {
            val = new int[10];  // Change: create and assign a new array to the parameter variable
            val[0] = 100;
        }
        static void Main()
        {
            string input = "original";
            Console.WriteLine(input);
            Function(ref input);      // need ref to modify the input
            Console.WriteLine(input);
    
            int[] val = new int[10];
            val[0] = 1;
            Function2(val);
            Console.WriteLine(val[0]); // This line still prints 1, not 100!
        }
    }
    

    现在,数组测试“失败”,因为您将一个新对象分配给非 ref 参数变量。

    【讨论】:

      【解决方案2】:

      string 与类似string 但可变的类型进行比较会有所帮助。让我们看一个StringBuilder 的简短示例:

      public void Caller1()
      {
          var builder = new StringBuilder("input");
          Console.WriteLine("Before: {0}", builder.ToString());
          ChangeBuilder(builder);
          Console.WriteLine("After: {0}", builder.ToString());
      }
      
      public void ChangeBuilder(StringBuilder builder)
      {
          builder.Clear();
          builder.Append("output");
      }
      

      这会产生:

      Before: input
      After: output
      

      所以我们看到,对于可变类型,即可以修改其值的类型,可以将对该类型的引用传递给ChangeBuilder 之类的方法,而不使用refout 和在我们调用它之后仍然会更改值。

      请注意,我们实际上从未将builder 设置为ChangeBuilder 中的其他值。

      相比之下,如果我们对字符串做同样的事情:

      public void Caller2()
      {
          var s = "input";
          Console.WriteLine("Before: {0}", s);
          TryToChangeString(s);
          Console.WriteLine("After: {0}", s);
      }
      
      public void TryToChangeString(string s)
      {
          s = "output";
      }
      

      这会产生:

      Before: input
      After: input
      

      为什么?因为在TryToChangeString 中,我们实际上并没有改变变量s 引用的字符串的内容,所以我们用一个全新的字符串替换 s。此外,sTryToChangeString 的局部变量,因此替换 s 的值 inside 函数对传递给函数调用的变量没有影响。

      因为string不可变的,所以如果不使用refout,就无法影响调用者字符串。

      最后,最后一个例子用string做我们想要的:

      public void Caller3()
      {
          var s = "input";
          Console.WriteLine("Before: {0}", s);
          ChangeString(ref s);
          Console.WriteLine("After: {0}", s);
      }
      
      public void ChangeString(ref string s)
      {
          s = "output";
      }
      

      这会产生:

      Before: input
      After: output
      

      ref 参数实际上使两个s 变量彼此别名。就好像它们是同一个变量

      【讨论】:

        【解决方案3】:

        字符串是不可变的 - 您不是在修改字符串,而是将引用指向的对象替换为另一个对象。

        将其与例如列表进行比较:要添加项目,您不需要参考。要将整个列表替换为不同的对象,您需要 ref(或 out)。

        【讨论】:

        • 字符串不可变的事实与这里无关。 int[] 是可变的,但也会遇到同样的问题。所有引用类型都存在相同的问题。但是,您的其他解释是正确且有帮助的。
        【解决方案4】:

        这适用于所有不可变类型。 string 恰好是不可变的。

        为了改变方法之外的不可变类型,你必须改变引用。因此需要refout 在方法之外产生效果。

        注意:值得注意的是,在您的示例中,您调用了与另一个示例不匹配的特定情况:您实际上指向的是不同的引用,而不是简单地更改现有引用。正如 dlev(以及我的 cmets 中的 Skeet 本人)所指出的,如果您对 all 其他类型(例如,val = new int[1])(包括可变类型)执行相同操作,那么您将“丢失”您的更改一旦方法返回,因为它们没有发生在内存中的同一个对象上,除非您使用 refout,就像上面对 string 所做的那样。

        希望澄清:

        您正在传递一个指向内存中对象的指针。如果没有refout,则会创建一个指向完全相同位置的新指针,并且所有更改都使用复制的指针进行。使用它们时,会使用相同的指针,并且对指针所做的所有更改都会反映在方法之外。

        如果您的对象是可变的,那么这意味着它可以在不创建对象的新实例的情况下进行更改。如果你创建一个新实例,那么你必须指向内存中的其他地方,这意味着你必须改变你的指针。

        现在,如果您的对象是不可变的,那么这意味着如果不创建新实例就无法更改它。

        在您的示例中,您创建了一个 string 的新实例(等于 "modified"),然后更改了指针 (input) 以指向该新实例。对于int 数组,您更改了val 有效指向的10 个值中的一个,这不需要弄乱val 的指针——它只是转到您想要的位置(数组的第一个元素),然后就地修改第一个值。

        一个更类似的例子是(从 dlev 窃取,但这是使它们真正具有可比性的方法):

        static void Function(ref string input)
        {
            input = "modified";
        }
        
        static void Function2(int[] val)
        {
            val = new int[1];
            val[0] = 100;
        }
        

        这两个函数都改变了它们的参数指针。只是因为您使用了refinput 才会“记住”它的更改,因为当它更改指针时,它正在更改传入的指针,而不是只是它的副本。 p>

        val 在函数之外仍然是一个由 10 个 ints 组成的数组,而 val[0] 仍然是 1,因为 Function2 中的“val”是一个不同 最初指向与 Main 的 val 相同位置的指针,但在新数组创建后它指向其他位置(不同的指针指向新数组,而原来的指针继续指向到同一个位置)。

        如果我使用 refint 数组,那么它会改变。而且它的大小也会发生变化。

        【讨论】:

        • 这里的不变性有点像红鲱鱼。重要的一点是要注意改变参数的值和改变参数的值所引用的对象的内容之间的区别。
        • @Jon 我实际上不同意这是一条红鲱鱼。每次使用 string 都需要设置值(例如,input = input.Replace(...);),以便希望在方法内或在方法外更改它。我认为这个问题表明发帖人理解对对象的操作改变了其他引用类型,例如他的数组示例,他根本不明白为什么用string 这样做没有相同的结果(并且使用了一个糟糕的例子)。但是,我相信您和 dlev 都提出了 prosseek 在提出问题时可能没有考虑到的有效观点。
        • 但是您的回答表明可变类型与不可变类型的行为不同 - 而它们没有。如果您为 StringBuilder 参数分配新值(而不是修改对象),那么也不会看到。这是重要的一点。对于不可变类型,可以进行任何其他类型的更改,但赋值的行为方式不依赖于不可变性。
        • @Jon 这是一个公平的观点,我想我可以看到这个例子,但不是问题;我只是更相信问题而不是例子。由于他确实发布了示例,因此我添加了一个大注释来澄清并可能重复 dlev。我想这就是你写书并注意我的原因:)。希望他能理解指针。
        • 这个问题与不可变性完全无关。这个答案具有误导性。
        【解决方案5】:

        新手的一个更好的例子:

        string a = "one";
        string b = a;
        string b = "two";
        
        Console.WriteLine(a);
        

        ... 将输出"one"

        为什么?因为您将一个全新的字符串分配给指针b

        【讨论】:

          【解决方案6】:

          令人困惑的是,默认情况下,ref 类型引用是按值传递的,要修改引用本身(对象指向的内容),您必须按引用传递引用 - 使用 ref

          在您的情况下,您正在处理字符串-将字符串分配给变量(或附加到等)会更改引用,因为字符串是不可变的,因此也无法避免这种情况,因此您必须使用ref .

          【讨论】:

            【解决方案7】:

            你是对的。数组和字符串是引用类型。但如果说实话并比较类似的行为,你应该这样写:

            static void Function2(int[] val) // It doesn't need 'ref' for a reference type
            {
                val = new[] { 1, 2, 3, 4 };
            }
            

            但在您的示例中,您通过引用 val 在 C# 一维数组的某些元素中执行写入操作。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-03-14
              • 1970-01-01
              • 1970-01-01
              • 2012-07-30
              • 2015-05-15
              • 1970-01-01
              相关资源
              最近更新 更多