7

C# 中的string类型是引用类型,通过值传递引用类型参数会复制引用,因此我不需要使用ref修饰符。但是,我需要使用ref修饰符来修改 input 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]);
    }
}
4

7 回答 7

14

您需要引用字符串参数的原因是,即使您传入对字符串对象的引用,为参数分配其他内容只会替换当前存储在参数变量中的引用。换句话说,您更改了参数所指的内容,但原始对象未更改。

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

编辑:请注意,虽然 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 = 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 参数变量。

于 2011-05-29T05:51:37.130 回答
6

它有助于与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在我们调用它之后仍然改变值。

请注意,我们实际上从未builderChangeBuilder.

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

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此外,s是一个局部变量TryToChangeString,因此替换函数s 内部的值对传递给函数调用的变量没有影响。

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

最后,最后一个例子做了我们想要的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变量互为别名。就好像它们是同一个变量

于 2011-05-29T06:18:53.903 回答
5

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

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

于 2011-05-29T05:49:17.617 回答
3

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

为了在方法之外更改不可变类型,您必须更改引用。因此,要么 要么ref需要out在方法之外产生效果。

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

希望澄清:

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

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

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

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

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

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

static void Function2(int[] val)
{
    val = new int[1];
    val[0] = 100;
}

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

valint在函数外部仍然是一个 10 s的数组,并且val[0]仍然是 1,因为其中的 " val"Function2是一个不同的指针,它最初指向与 Main 相同的位置val,但在创建新数组后它指向其他位置(不同的指针指向新的数组,而原来的指针继续指向同一个位置)。

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

于 2011-05-29T05:49:31.620 回答
1

新手的一个更好的例子:

string a = "one";
string b = a;
string b = "two";

Console.WriteLine(a);

...将输出"one".

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

于 2011-05-30T13:27:35.280 回答
0

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

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

于 2011-05-29T05:48:44.117 回答
0

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

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

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

于 2015-07-23T21:22:58.263 回答