Answers:
引用已传递;但是,从技术上讲,它不是通过引用传递的。这是一个微妙但非常重要的区别。考虑以下代码:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
您需要了解三件事才能了解此处发生的情况:
strMain
也不会通过引用传递。这是一个引用类型,但是引用本身是通过value传递的。每当您传递不带ref
关键字的参数(不计算out
参数)时,您都按值传递了一些东西。因此,这必须表示您正在...按值传递引用。由于它是引用类型,因此仅将引用复制到堆栈中。但是,这是什么意思?
C#变量是引用类型或值类型。C#参数通过引用传递或通过value传递。这里的术语是一个问题。这些听起来像是同一件事,但事实并非如此。
如果您传递的是ANY类型的参数,并且您没有使用ref
关键字,那么您已经按值传递了它。如果您按值传递了它,那么您真正传递的就是一个副本。但是,如果参数是引用类型,那么您复制的内容就是引用,而不是指向的内容。
这是方法的第一行Main
:
string strMain = "main";
我们在此行上创建了两件事:一个字符串,其值main
存储在内存中的某个位置,以及一个称为strMain
指向它的引用变量。
DoSomething(strMain);
现在,我们将该引用传递给DoSomething
。我们已经按值传递了它,所以这意味着我们做了一个副本。这是一个引用类型,这意味着我们复制了引用,而不是字符串本身。现在我们有两个引用,每个引用指向内存中的相同值。
这是该DoSomething
方法的顶部:
void DoSomething(string strLocal)
没有ref
关键字,所以strLocal
和strMain
是指向相同值的两个不同引用。如果我们重新分配strLocal
...
strLocal = "local";
...我们尚未更改储值;我们以这个参考为参考,strLocal
并将其瞄准了全新的弦乐。strMain
当我们这样做时会发生什么?没有。它仍然指向旧字符串。
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
让我们暂时更改场景。想象一下,我们不是在使用字符串,而是使用一些可变的引用类型,例如您创建的类。
class MutableThing
{
public int ChangeMe { get; set; }
}
如果遵循对objLocal
它所指向的对象的引用,则可以更改其属性:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
MutableThing
内存中仍然只有一个,复制的参考和原始参考仍然指向它。本身的属性MutableThing
已更改:
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
啊,但是字符串是不可变的!没有ChangeMe
要设置的属性。您无法strLocal[3] = 'H'
像使用C样式char
数组那样在C#中进行操作;您必须构造一个全新的字符串。更改的唯一方法strLocal
是将引用指向另一个字符串,这意味着您所做的任何事情都不会strLocal
影响strMain
。该值是不可变的,并且引用是副本。
为了证明两者之间存在差异,当您通过引用传递引用时,会发生以下情况:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
这次,in中的字符串Main
确实确实发生了变化,因为您传递了引用而未将其复制到堆栈中。
因此,即使字符串是引用类型,按值传递它们也意味着被调用方中发生的任何事情都不会影响调用方中的字符串。但是由于它们是引用类型,所以当您要将其传递给内存时,您不必复制整个字符串。
ref
关键字。要证明通过引用传递效果会有所不同,请参见此演示:rextester.com/WKBG5978
ref
关键字具有实用性,我只是想解释一下为什么人们可能会想到在C#中按值传递引用类型似乎像是按引用传递(并传递引用类型)的“传统”(即C)概念C#中的按引用似乎更像是按值将引用传递给引用)。
Foo(string bar)
可以被看作Foo(char* bar)
而Foo(ref string bar)
将Foo(char** bar)
(或Foo(char*& bar)
或Foo(string& bar)
C ++中)。当然,这不是您每天都应该如何看待它,但是它实际上帮助我最终了解了幕后的情况。
C#中的字符串是不可变的引用对象。这意味着对它们的引用将被传递(按值),并且一旦创建了字符串,就无法对其进行修改。产生字符串的修改版本(子字符串,修剪的版本等)的方法将创建原始字符串的修改副本。
字符串是特殊情况。每个实例都是不可变的。当您更改字符串的值时,您将在内存中分配新的字符串。
因此,仅将引用传递给您的函数,但是在编辑字符串时,它将成为新实例,并且不会修改旧实例。
Uri
&不同,Guid
您可以将字符串文字值分配给字符串变量。字符串似乎是可变的,就像int
被重新分配一样,但是它隐式地创建了一个对象-没有new
关键字。