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

Col*_*son 114 .net string function parameter-passing

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

Jus*_*gan 262

要回答您的问题,请考虑以下代码:

void Main()
{
    string strMain = "main";
    DoSomething(strMain);
    Console.Write(strMain); // What gets printed?
}
void DoSomething(string strLocal)
{
    strLocal = "local";
}
Run Code Online (Sandbox Code Playgroud)

为了预测这里会发生什么,并了解其原因,您需要了解三件事.

  1. 字符串是C#中的引用类型.但这只是图片的一部分.
  2. 它们也是不可变的,所以每当你做一些看起来像是在改变字符串的东西时,你就不是.创建一个全新的字符串,引用指向它,旧的字符串被抛弃.
  3. 即使字符串是引用类型,strMain也不通过引用传递.它是一个引用类型,但引用是按值传递的.这是一个棘手的区别,但它是一个至关重要的区别.每当你传递一个没有ref关键字的参数(不计算out参数)时,你就按值传递了一些东西.

但是,这是什么意思?

按值传递引用类型:您已经在执行此操作

C#中有两组数据类型:引用类型值类型.在C#中还有两种传递参数的方法:按引用按值传递.这听起来一样,容易混淆.它们不是同一件事!

如果传递任何类型的参数,并且不使用该ref关键字,那么您已按值传递它.如果你按值传递它,你真正传递的是副本.但是如果参数是引用类型,那么你复制的东西就是引用,而不是它指向的东西.

这是我们Main方法的第一行:

string strMain = "main";
Run Code Online (Sandbox Code Playgroud)

实际上我们在这一行上创建了两件事:一个字符串,其值main存储在某个地方的内存中,一个名为strMain指向它的引用变量.

DoSomething(strMain);
Run Code Online (Sandbox Code Playgroud)

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

在被叫里面

这是DoSomething方法的顶部:

void DoSomething(string strLocal)
Run Code Online (Sandbox Code Playgroud)

ref像往常一样没有关键字.所以strLocal不是strMain,但他们都指向同一个地方.如果我们"改变" strLocal,就像这样......

strLocal = "local";   
Run Code Online (Sandbox Code Playgroud)

......我们本身没有改变储值.我们重新指出了这个参考.我们接受了所谓的参考,strLocal并将其瞄准了一个全新的字符串.strMain当我们这样做时会发生什么?没有.它仍然指着旧的弦!

string strMain = "main"; //Store a string, create a reference to it
DoSomething(strMain);    //Reference gets copied, copy gets re-pointed
Console.Write(strMain);  //The original string is still "main" 
Run Code Online (Sandbox Code Playgroud)

不可变性很重要

让我们改变场景一秒钟.想象一下,我们不使用字符串,而是使用一些可变引用类型,就像您创建的类一样.

class MutableThing
{
    public int ChangeMe { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

如果您按照objLocal它指向的对象的引用,您可以更改其属性:

void DoSomething(MutableThing objLocal)
{
     objLocal.ChangeMe = 0;
} 
Run Code Online (Sandbox Code Playgroud)

MutableThing内存中只有一个,复制的引用和原始引用仍然指向它.自身的属性MutableThing已经改变:

void Main()
{
    var objMain = new MutableThing();
    objMain.ChangeMe = 5; 
    Console.Write(objMain.ChangeMe);  //it's 5 on objMain

    DoSomething(objMain);             //now it's 0 on objLocal
    Console.Write(objMain.ChangeMe);  //it's also 0 on objMain   
}
Run Code Online (Sandbox Code Playgroud)

啊,但......

......字符串是不可变的!没有ChangeMe可设置的属性.你不能strLocal[3] = 'H';像C风格的char数组那样做; 你必须改为构造一个全新的字符串.改变的唯一方法strLocal是将引用指向另一个字符串,这意味着你所做的任何事情都strLocal无法影响strMain.该值是不可变的,引用是副本.

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

更多资源:

  • @TheLight - 抱歉,当你说:"默认情况下通过引用传递引用类型时,你在这里是不正确的." 默认情况下,所有参数都按值传递,但是使用引用类型,这意味着*引用按值传递.*您将引用类型与引用参数混淆,这是可以理解的,因为它是一个非常混乱的区别.请参阅此处的[按价值传递参考类型部分.](http://msdn.microsoft.com/en-us/library/s6938f28(v = vs.110).aspx)您的链接文章非常正确,但实际上支持我的观点. (3认同)
  • @JustinMorgan 不是要提出一个死评论线程,但我认为如果你用 C 语言思考,TheLight 的评论是有意义的。在 C 语言中,数据只是一个内存块。引用是指向该内存块的指针。如果将整个内存块传递给函数,则称为“按值传递”。如果传递指针,则称为“按引用传递”。在 C# 中,没有传入整个内存块的概念,因此他们重新定义了“按值传递”,意思是传入指针。这似乎是错误的,但指针也只是一块内存!对我来说,这个术语相当随意 (2认同)
  • @JustinMorgan 我同意混合 C 和 C# 术语是不好的,但是,虽然我喜欢 lippert 的帖子,但我不同意将引用视为指针会特别混淆这里的任何内容。博客文章描述了如何将引用视为指针赋予它太多的权力。我知道`ref` 关键字具有实用性,我只是想解释为什么_可能_认为在 C# 中按值传递引用类型似乎是按引用传递(和传递)的“传统”(即 C)概念C# 中按引用的引用类型似乎更像是按值传递对引用的引用)。 (2认同)
  • 你是对的,但我认为@roliu 是指如何将诸如`Foo(string bar)` 之类的函数视为`Foo(char* bar)` 而`Foo(ref string bar)` 将是`Foo( char** bar)`(或 C++ 中的 `Foo(char*& bar)` 或 `Foo(string& bar)`)。当然,这不是你每天应该怎么想的,但它实际上帮助我最终了解了幕后发生的事情。 (2认同)

das*_*ght 23

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


Eni*_*ity 10

字符串是特殊情况.每个实例都是不可变的.当您更改字符串的值时,您将在内存中分配新字符串.

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

  • 在这方面,字符串不是*特殊情况.创建可以具有相同语义的不可变对象非常容易.(也就是说,一个不暴露变异方法的类型的实例......) (4认同)
  • @pst - 字符串具有特殊的创建语义 - 与`Uri`和`Guid`不同 - 您只需将字符串文字值分配给字符串变量即可.该字符串似乎是可变的,就像`int`被重新分配一样,但是它隐式地创建了一个对象 - 没有`new`关键字. (3认同)
  • 字符串是一种特殊情况,但与此问题无关.值类型,引用类型,无论何种类型都将在此问题中表现相同. (3认同)