在.NET 中字符串是如何传递的?

当我向函数传递 string时,是传递指向字符串内容的指针,还是像传递 struct一样将整个字符串传递给堆栈上的函数?

56077 次浏览

C # 中的字符串是不可变的引用对象。这意味着对它们的引用是通过值传递的,一旦创建了字符串,就不能修改它。生成字符串修改版本(子字符串、修剪版本等)的方法创建原始字符串的修改后的 副本

字符串是特例。每个实例都是不可变的。当您更改字符串的值时,您正在内存中分配一个新字符串。

因此,只有引用被传递给函数,但是当字符串被编辑时,它就变成了一个新的实例,并且不会修改旧的实例。

传递了一个引用; 但是,它不是技术上的 通过引用传递。。这是一个微妙但非常重要的区别。考虑以下代码:

void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}

要理解这里发生的事,你需要知道三件事:

  1. 字符串是 C # 中的引用类型。
  2. 它们也是不可变的,所以任何时候你做的事情看起来像是在改变字符串,其实不是。一个全新的字符串被创建,引用指向它,旧的字符串被丢弃。
  3. 即使字符串是引用类型,strMain也不是通过引用传递的。这是一个 引用类型,,但参考本身是 通过值传递。任何时候传递一个没有 ref关键字的参数(不计算 out参数) ,都是通过值传递。

所以这一定意味着你在... 通过值传递一个引用。因为它是引用类型,所以只有引用被复制到堆栈上。那是什么意思?

通过值传递引用类型: 您已经在这样做了

C # 变量是 引用类型值类型。C # 参数是 通过引用传递通过值传递。术语在这里是个问题,它们听起来是一样的,但实际上不是。

如果您传递的参数是 ANY 类型的,并且您没有使用 ref关键字,那么您已经通过值传递了它。如果通过值传递它,那么实际上传递的是一个副本。但是如果参数是引用类型,那么您复制的是 参考文献,,而不是它指向的任何东西。

下面是 Main方法的第一行:

string strMain = "main";

我们在这一行中创建了两个东西: 一个值为 main的字符串存储在内存的某个地方,以及一个名为 strMain的引用变量指向它。

DoSomething(strMain);

现在我们将该引用传递给 DoSomething。我们已经通过值传递了它,这意味着我们复制了一个副本。它是一个引用类型,这意味着我们复制的是引用,而不是字符串本身。现在我们有两个引用,每个引用都指向内存中相同的值。

在被调用者体内

下面是 DoSomething方法的顶部:

void DoSomething(string strLocal)

没有 ref关键字,所以 strLocalstrMain是指向同一个值的两个不同的引用。如果我们重新分配 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属性。您不能像使用 C 样式的 char数组那样在 C # 中执行 strLocal[3] = 'H'; 您必须构造一个全新的字符串。更改 strLocal的唯一方法是将引用指向另一个字符串,这意味着对 strLocal所做的任何操作都不会影响 strMain。该值是不可变的,引用是一个副本。

通过引用传递引用

为了证明两者之间的区别,下面是发生的 当你通过引用传递引用时:

void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain);          // Prints "local"
}

这一次,Main中的字符串确实发生了变化,因为您传递了引用,而没有将其复制到堆栈上。

因此,即使字符串是引用类型,通过值传递它们意味着在被调用方中发生的任何事情都不会影响调用方中的字符串。但是因为它们是 引用类型,所以当您想要传递整个字符串时,不必在内存中复制整个字符串。

其他资源: