C# 连接字符串时的新引用

C# 连接字符串时的新引用,c#,variables,memory-management,stack,clr,C#,Variables,Memory Management,Stack,Clr,几周前,我在一次求职面试中被问到一个问题。问题恰恰是这样的: string a = "Hello, "; for(int i = 0; i < 99999999; i++) { a += "world!"; } string a=“你好,”; 对于(int i=0;i

几周前,我在一次求职面试中被问到一个问题。问题恰恰是这样的:

string a = "Hello, ";

for(int i = 0; i < 99999999; i++)
{
    a += "world!";
}
string a=“你好,”;
对于(int i=0;i<9999999;i++)
{
a+=“世界!”;
}
我被确切地问到,“为什么这是一个连接字符串的坏方法?”。我的回答是“可读性,应该选择附加”等等

但据采访我的那个人说,显然情况并非如此。根据他的说法,由于CLR的结构,每次我们连接一个字符串时,都会在内存中创建一个新的引用。因此,在下面代码的末尾,我们将在内存中存储9999999个字符串变量“a”

我认为,只要给对象赋值,对象在堆栈中只创建一次(我不是说堆)。我知道的方式是,内存分配在堆栈中为每个基本数据类型执行一次,它们的值会根据需要进行修改,并在范围执行完成时进行处理。这不对吗?或者,变量“a”的新引用是否每次连接时都在堆栈中创建

有人能解释一下stack的工作原理吗?非常感谢。

(即类和字符串)总是在堆中创建的。值类型(如结构)在堆栈中创建,并在函数结束执行时丢失

然而,在循环之后,声明内存中将有N个对象并不完全正确。在每次评估

a += "world!";
语句创建一个新字符串。对先前创建的字符串的处理更为复杂。垃圾收集器现在拥有它,因为在您的代码中没有其他对它的引用,它将在某个点释放,您不知道什么时候会发生

最后,这段代码的最终问题是,您认为您正在修改一个对象,但字符串是不可变的,这意味着您无法在创建后真正更改它们的值。您只能创建新的,这就是+=操作符正在做的。如果使用
StringBuilder
,这将更加高效,因为它是可变的

编辑

根据要求,下面是与堆栈/堆相关的说明。值类型并不总是在堆栈中。在函数体中声明它们时,它们位于堆栈中:

void method()
{
    int a = 1; // goes in the stack
}
但是当它们是其他对象的一部分时,就进入堆中,比如当整数是类的属性时(因为整个类实例都在堆中)。

(即类和字符串)总是在堆中创建的。值类型(如结构)在堆栈中创建,并在函数结束执行时丢失

然而,在循环之后,声明内存中将有N个对象并不完全正确。在每次评估

a += "world!";
语句创建一个新字符串。对先前创建的字符串的处理更为复杂。垃圾收集器现在拥有它,因为在您的代码中没有其他对它的引用,它将在某个点释放,您不知道什么时候会发生

最后,这段代码的最终问题是,您认为您正在修改一个对象,但字符串是不可变的,这意味着您无法在创建后真正更改它们的值。您只能创建新的,这就是+=操作符正在做的。如果使用
StringBuilder
,这将更加高效,因为它是可变的

编辑

根据要求,下面是与堆栈/堆相关的说明。值类型并不总是在堆栈中。在函数体中声明它们时,它们位于堆栈中:

void method()
{
    int a = 1; // goes in the stack
}

但是当它们是其他对象的一部分时,就进入堆中,比如当整数是类的属性时(因为整个类实例都在堆中)。

.NET区分ref和value类型<代码>字符串是引用类型。它毫无例外地在堆上分配。它的生命周期由GC控制

因此,在下面代码的末尾,我们将在内存中存储9999999个字符串变量“a”

已分配9999999。当然,其中一些可能已经被GC’ed了

它们的值会根据需要进行修改,并在范围执行完成时进行处理

字符串不是基元或值类型。这些被“内联”分配到其他对象(如堆栈、数组或堆对象)的内部。它们也可以装箱并成为真正的堆对象。所有这些在这里都不适用


这段代码的问题不是分配,而是二次运行时复杂性。我认为这个循环在实践中永远不会结束。

.NET区分了ref和value类型<代码>字符串是引用类型。它毫无例外地在堆上分配。它的生命周期由GC控制

因此,在下面代码的末尾,我们将在内存中存储9999999个字符串变量“a”

已分配9999999。当然,其中一些可能已经被GC’ed了

它们的值会根据需要进行修改,并在范围执行完成时进行处理

字符串不是基元或值类型。这些被“内联”分配到其他对象(如堆栈、数组或堆对象)的内部。它们也可以装箱并成为真正的堆对象。所有这些在这里都不适用


这段代码的问题不是分配,而是二次运行时复杂性。我认为这个循环在实践中永远不会结束。

首先记住以下两个事实:

  • string
    是不可变的类型(现有实例永远不会被修改)
  • string
    是引用类型(
    string
    表达式的“值”是对实例所在位置的引用)
因此,有这样一种说法:

a += "world!";
将类似于
a=a+“world!”。它将首先跟随对“old”
a
的引用,并用stri连接该旧字符串