【发布时间】:2012-06-03 06:58:03
【问题描述】:
当我将string 传递给函数时,是指向传递的字符串内容的指针,还是像struct 那样传递给堆栈上的函数的整个字符串?
【问题讨论】:
标签: .net string function parameter-passing
当我将string 传递给函数时,是指向传递的字符串内容的指针,还是像struct 那样传递给堆栈上的函数的整个字符串?
【问题讨论】:
标签: .net string function parameter-passing
一个引用被传递;但是,从技术上讲,它并不是通过引用传递的。这是一个微妙但非常重要的区别。考虑以下代码:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
要了解这里发生的事情,您需要知道三件事:
strMain 也不会通过引用传递。它是一个引用类型,但引用本身是按值传递。任何时候你传递一个没有ref 关键字的参数(不包括out 参数),你就传递了值。所以这一定意味着您正在...按值传递引用。由于它是引用类型,因此只有引用被复制到堆栈中。但这意味着什么?
C# 变量是引用类型或值类型。 C# 参数要么按引用传递,要么按值传递。术语在这里是个问题。这些听起来像是同一件事,但事实并非如此。
如果你传递了一个 ANY 类型的参数,并且你没有使用 ref 关键字,那么你已经通过值传递了它。如果您按值传递它,那么您真正传递的是一个副本。但如果参数是引用类型,那么您复制的内容就是引用,而不是它指向的任何内容。
这是Main 方法的第一行:
string strMain = "main";
我们在这一行创建了两件事:一个字符串,其值 main 存储在内存中某处,以及一个名为 strMain 的引用变量指向它。
DoSomething(strMain);
现在我们将该引用传递给DoSomething。我们已经按值传递了它,这意味着我们制作了一个副本。它是一个引用类型,这意味着我们复制了引用,而不是字符串本身。现在我们有两个引用,每个引用都指向内存中的相同值。
这是DoSomething方法的顶部:
void DoSomething(string strLocal)
没有ref 关键字,所以strLocal 和strMain 是指向相同值的两个不同引用。如果我们重新分配strLocal...
strLocal = "local";
...我们没有更改存储的值;我们采用了名为strLocal 的引用并将其瞄准了一个全新的字符串。当我们这样做时,strMain 会发生什么? Nothing. It's still pointing at the old string.
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,并且复制的引用和原始引用仍然指向它。 The properties of the MutableThing itself have changed:
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。值是不可变的,引用是副本。
为了证明有区别,下面是when you pass a reference by reference:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
这一次,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(结构)也是特例。我看不出System.String 与其他不可变类型相比如何表现得更像“值类型”......无论是类还是结构起源。
Uri 和 Guid 不同 - 您可以将字符串文字值分配给字符串变量。该字符串似乎是可变的,就像重新分配 int 一样,但它隐式创建了一个对象 - 没有 new 关键字。