String是一种引用类型,尽管它具有值类型的大多数特征,例如不可变和重载==以比较文本,而不是确保它们引用同一个对象。

为什么字符串不只是一个值类型呢?


当前回答

这是对一个老问题的迟来的回答,但所有其他的回答都忽略了一点,那就是。net直到2005年的。net 2.0才有泛型。

String是一种引用类型,而不是值类型,因为对于Microsoft来说,确保字符串能够以最有效的方式存储在非泛型集合(如System.Collections.ArrayList)中至关重要。

在非泛型集合中存储值类型需要对类型对象进行特殊的转换,这种转换称为装箱。当CLR装箱一个值类型时,它将该值包装在系统中。对象,并将其存储在托管堆上。

从集合中读取值需要反向操作,称为开箱操作。

装箱和拆箱都有不可忽略的成本:装箱需要额外的分配,拆箱需要类型检查。

一些答案错误地声称字符串永远不可能被实现为值类型,因为它的大小是可变的。实际上,将字符串实现为包含两个字段的固定长度数据结构是很容易的:字符串长度的整数和指向char数组的指针。你也可以在此基础上使用小字符串优化策略。

如果泛型从第一天就存在,我想将字符串作为值类型可能是一个更好的解决方案,具有更简单的语义,更好的内存使用和更好的缓存位置。只包含小字符串的List<string>可能是单个连续的内存块。

其他回答

这是对一个老问题的迟来的回答,但所有其他的回答都忽略了一点,那就是。net直到2005年的。net 2.0才有泛型。

String是一种引用类型,而不是值类型,因为对于Microsoft来说,确保字符串能够以最有效的方式存储在非泛型集合(如System.Collections.ArrayList)中至关重要。

在非泛型集合中存储值类型需要对类型对象进行特殊的转换,这种转换称为装箱。当CLR装箱一个值类型时,它将该值包装在系统中。对象,并将其存储在托管堆上。

从集合中读取值需要反向操作,称为开箱操作。

装箱和拆箱都有不可忽略的成本:装箱需要额外的分配,拆箱需要类型检查。

一些答案错误地声称字符串永远不可能被实现为值类型,因为它的大小是可变的。实际上,将字符串实现为包含两个字段的固定长度数据结构是很容易的:字符串长度的整数和指向char数组的指针。你也可以在此基础上使用小字符串优化策略。

如果泛型从第一天就存在,我想将字符串作为值类型可能是一个更好的解决方案,具有更简单的语义,更好的内存使用和更好的缓存位置。只包含小字符串的List<string>可能是单个连续的内存块。

字符串不是值类型,因为它们可能很大,需要存储在堆上。值类型(在CLR的所有实现中)存储在堆栈上。堆栈分配字符串会破坏各种各样的事情:32位的堆栈只有1MB, 64位的堆栈只有4MB,你必须装箱每个字符串,导致复制惩罚,你不能实习字符串,内存使用会膨胀,等等……

(编辑:增加了关于值类型存储是实现细节的说明,这导致了这种情况,即我们有一个具有值语义的类型没有从System.ValueType继承。由于本。)

它不是一个值类型,因为如果它是一个值类型,并且它的值每次传递给方法或从方法返回时都必须复制,那么性能(空间和时间!)会很糟糕。

它有价值语义来保持世界的理智。你能想象编码有多困难吗

string s = "hello";
string t = "hello";
bool b = (s == t);

设b为假?想象一下编写任何应用程序是多么困难。

并不像字符串由字符数组组成那么简单。我把字符串看作字符数组[]。因此,它们位于堆上,因为引用内存位置存储在堆栈上,并且指向数组在堆上内存位置的开始。字符串大小在分配之前是不知道的…非常适合堆。

这就是为什么字符串是不可变的,因为当你改变它时,即使它的大小相同,编译器也不知道,它必须分配一个新的数组,并将字符分配到数组中的位置。如果你认为字符串是语言保护你不需要动态分配内存的一种方式,这是有道理的(像编程一样阅读C语言)

此外,字符串的实现方式(每个平台都不同)以及何时开始将它们拼接在一起。就像使用StringBuilder一样。它为你分配了一个缓冲区供你复制,一旦你到达终点,它会为你分配更多的内存,希望如果你做一个大的连接性能不会受到阻碍。

也许乔恩·斯基特可以过来帮忙?