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

推荐答案

传递引用;然而,从技术上讲,这不是passed by reference.这是一个微妙的,但非常重要的区别.考虑下面的代码:

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

要了解这里发生的事情,您需要了解三件事:

  1. 字符串是C#中的引用类型.
  2. 它们也是不可变的,所以任何时候你做的事情看起来像是在更改字符串,你都不是.一个全新的字符串被创建,引用被指向它,旧的字符串被丢弃.
  3. 即使字符串是引用类型,strMain也不是通过引用传递的.它是reference type,,但参考本身是passed by value.任何时候,当你传递一个没有ref关键字的参数(不包括out个参数)时,你就按值传递了一些东西.

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

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

C#变量可以是reference typesvalue types.C#参数为passed by referencepassed by value.术语在这里是个问题;这些听起来是一样的,但事实并非如此.

如果您传递了任何类型的参数,并且没有使用ref关键字,那么您是按值传递的.如果你通过值传递,你真正传递的是一个副本.但是如果参数是引用类型,那么你复制的是reference,,而不是它指向的任何东西.

下面是Main方法的第一行:

string strMain = "main";

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

DoSomething(strMain);

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

在被叫人里面

下面是DoSomething种方法中的前几种:

void DoSomething(string strLocal)

没有ref关键字,所以strLocalstrMain是指向相同值的两个不同引用.如果我们重新分配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设置为follow,则可以更改其属性:

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-style 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中的字符串确实发生了更改,因为您传递了引用,而没有在堆栈上复制它.

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

其他资源:

.net相关问答推荐

SLN配置文件:映射问题

从 Contentful 中的富文本元素中获取价值?

.NET MAUI 的登录页面

将字符串与容差进行比较

如何在 FtpWebRequest 之前判断 FTP 上是否存在文件

既然 .NET 有一个垃圾收集器,为什么我们需要终结器/析构器/dispose-pattern?

C# 的部分类是糟糕的设计吗?

BackgroundWorker 中未处理的异常

如何让 .NET 的 Path.Combine 将正斜杠转换为反斜杠?

新的 netstandardapp 和 netcoreapp TFM 有什么区别?

什么版本的 .NET 附带什么版本的 Windows?

我应该在 LINQ 查询中使用两个where子句还是&&?

我们应该总是在类中包含一个默认构造函数吗?

在关闭警告中访问 foreach 变量

让 String.Replace 只打整个单词的方法

获取系统中已安装的应用程序

无锁多线程适用于真正的线程专家

如何判断对象是否已在 C# 中释放

在 Windows 窗体 C# 应用程序中拥有配置文件的最简单方法

WPF 中的 Application.DoEvents() 在哪里?