在C#函数中键入'string'作为参数

时间:2011-05-29 05:46:36

标签: c# .net string ref

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]);
    }
}

7 个答案:

答案 0 :(得分:12)

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

当您参考参数时,您告诉函数该参数实际上是传入变量的别名,因此赋值给它将产生所需的效果。

编辑:请注意,虽然字符串是一个不可变的引用类型,但这里并不太重要。由于您只是尝试分配一个新对象(在这种情况下是字符串对象“已修改”),因此您的方法不适用于任何引用类型。例如,请考虑对您的代码稍作修改:

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参数变量分配一个新对象。

答案 1 :(得分:6)

stringstring类似但可变的类型进行比较会有所帮助。让我们看一下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的局部变量,因此替换<{1}} 内部的值,该函数对传入该变量的变量没有影响。功能调用。

由于s不可变的,因此无法使用stringref影响来电者字符串。

最后,最后一个示例使用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变量别名。就好像它们是相同的变量

答案 2 :(得分:5)

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

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

答案 3 :(得分:3)

所有不可变类型都是这种情况。 string恰好是不可改变的。

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

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

希望澄清:

您正在传入指向内存中对象的指针。如果没有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;
}

两个函数都会更改其参数的指针。只是因为您使用ref确实input“记住”其更改,因为当它更改指针时,它正在更改传入的指针而只是一个副本它

val仍然是函数外10 int的数组,而val[0]仍为1,因为val位于Function2内}是一个不同的指针,它最初指向与Main val相同的位置,但它在创建新数组后指向其他位置(不同的指针指向到新数组,原始指针继续指向同一位置。

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

答案 4 :(得分:0)

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

在你的情况下,你正在处理字符串 - 将一个字符串赋值给一个变量(或追加等)会改变引用,因为字符串是不可变的,也无法避免这种情况,所以你必须使用{{1 }}

答案 5 :(得分:0)

新手的一个更好的例子:

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

Console.WriteLine(a);

...将输出"one"

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

答案 6 :(得分:0)

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

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

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