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]);
}
}
答案 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)
将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
之类的方法,而不是使用ref
或out
并且在我们调用它之后仍然会更改值。
请注意,我们在任何时候都没有将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
。此外,s
是TryToChangeString
的局部变量,因此替换<{1}} 内部的值,该函数对传入该变量的变量没有影响。功能调用。
由于s
是不可变的,因此无法使用string
或ref
来影响来电者字符串。
最后,最后一个示例使用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
恰好是不可改变的。
要在方法外部更改不可变类型,必须更改引用。因此,ref
或out
需要在方法之外产生效果。
注意:值得注意的是,在您的示例中,您正在调用与另一个示例不匹配的特定情况:您实际上指向的是不同的引用,而不是简单地更改现有引用。正如dlev(以及我在评论中的Skeet本人)所指出的那样,如果你为所有其他类型(例如val = new int[1]
)做了同样的事情,包括可变的那些,那么你将“失去”方法返回后您的更改,因为它们不会发生在内存中的同一对象中,除非您像上面使用ref
一样使用out
或string
。
希望澄清:
您正在传入指向内存中对象的指针。如果没有ref
或out
,则会生成一个指向完全相同位置的新指针,并使用复制的指针进行所有更改。使用它们时,使用相同的指针,对指针所做的所有更改都会反映在方法之外。
如果您的对象是可变的,那么这意味着可以在不创建对象的新实例的情况下更改它。如果您创建一个新实例,那么您必须指向内存中的其他位置,这意味着您必须更改指针。
现在,如果您的对象是不可变的,那么这意味着如果不创建新实例就无法更改它。
在您的示例中,您创建了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
相同的位置,但它在创建新数组后指向其他位置(不同的指针指向到新数组,原始指针继续指向同一位置。
如果我将ref
与int
数组一起使用,那么它就会发生变化。它的大小也会发生变化。
答案 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#一维数组的某个元素中执行写操作。