NET中如何传递字符串?


Answers:


277

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

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

您需要了解三件事才能了解此处发生的情况:

  1. 字符串是C#中的引用类型。
  2. 它们也是不可变的,因此,只要您执行的操作看起来像在更改字符串,就不会更改。将创建一个全新的字符串,指向该字符串的引用,而将旧的字符串丢弃。
  3. 即使字符串是引用类型,strMain也不会通过引用传递。这是一个引用类型,但是引用本身是通过value传递的。每当您传递不带ref关键字的参数(不计算out参数)时,您都按值传递了一些东西。

因此,这必须表示您正在...按值传递引用。由于它是引用类型,因此仅将引用复制到堆栈中。但是,这是什么意思?

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

C#变量是引用类型值类型。C#参数通过引用传递通过value传递。这里的术语是一个问题。这些听起来像是同一件事,但事实并非如此。

如果您传递的是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要设置的属性。您无法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确实确实发生了变化,因为您传递了引用而未将其复制到堆栈中。

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

更多资源:


3
@TheLight-对不起,但是您在这里说“默认情况下,引用是通过引用传递的”是不正确的。默认情况下,所有参数都是按值传递的,但是对于引用类型,这意味着引用是按值传递的。您正在将引用类型与引用参数进行合并,这是可以理解的,因为这是一个非常令人困惑的区别。请参阅此处按值传递引用类型部分。您的链接文章非常正确,但实际上支持我的观点。
贾斯汀·摩根

1
@JustinMorgan不想提出一个无效的注释线程,但是我认为,如果您使用C语言,TheLight的注释是有意义的。在C语言中,数据只是一块内存。引用是指向该内存块的指针。如果将整个内存块传递给一个函数,则称为“按值传递”。如果传递指针,则称为“按引用传递”。在C#中,没有传递整个内存块的概念,因此它们重新定义了“按值传递”以表示将指针传入。这似乎是错误的,但是指针也只是一个内存块!对我来说,术语是任意的
rliu

@roliu-问题是我们不在C中工作,尽管C#的名称和语法相似,但C#却截然不同。一方面,引用与指针并不相同,以此类推可能会导致陷阱。但是,最大的问题是“按引用传递” 在C#中具有非常特殊的含义,需要ref关键字。要证明通过引用传递效果会有所不同,请参见此演示:rextester.com/WKBG5978
Justin Morgan

1
@JustinMorgan我同意将C和C#的术语混合使用是不好的,但是,尽管我喜欢lippert的文章,但我不同意将引用作为指针的思考在本文中引起了很大的混淆。博客文章描述了如何将引用视为指针赋予它太多的功能。我知道该ref关键字具有实用性,我只是想解释一下为什么人们可能会想到在C#中按值传递引用类型似乎像是按引用传递(并传递引用类型)的“传统”(即C)概念C#中的按引用似乎更像是按值将引用传递给引用)。
rliu

2
你是正确的,但我认为@roliu被引用怎样的功能,例如Foo(string bar)可以被看作Foo(char* bar)Foo(ref string bar)Foo(char** bar)(或Foo(char*& bar)Foo(string& bar)C ++中)。当然,这不是您每天都应该如何看待它,但是它实际上帮助我最终了解了幕后的情况。
科尔·约翰逊

23

C#中的字符串是不可变的引用对象。这意味着对它们的引用将被传递(按值),并且一旦创建了字符串,就无法对其进行修改。产生字符串的修改版本(子字符串,修剪的版本等)的方法将创建原始字符串的修改副本


10

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

因此,仅将引用传递给您的函数,但是在编辑字符串时,它将成为新实例,并且不会修改旧实例。


4
在这方面,字符串不是特殊情况。创建具有相同语义的不可变对象非常容易。(也就是说,一个类型的实例不会公开对其进行更改的方法...)

字符串是特殊情况-字符串实际上是不可变的引用类型,由于它们的行为类似于值类型,因此看起来是可变的。
令人作呕的2012年

1
@Enigmativity通过这种逻辑,Uri(类)和Guid(结构)也是特例。我没有看到System.String像其他“不可变类型”一样具有类或结构起源的行为。

3
@pst-字符串具有特殊的创建语义-与Uri&不同,Guid您可以将字符串文字值分配给字符串变量。字符串似乎是可变的,就像int被重新分配一样,但是它隐式地创建了一个对象-没有new关键字。
令人作呕的2012年

3
字符串是一种特殊情况,但与该问题无关。值类型,引用类型,任何类型在此问题中的作用都相同。
柯克·布罗德赫斯特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.