在C#中,为什么string的行为像一个值types的引用types?

一个string是一个引用types,即使它具有值types的大多数特征,例如不可变,并且有==重载来比较文本,而不是确保它们引用同一个对象。

为什么不是string只是一个值types呢?

string不是值types,因为它们可能很大,并且需要存储在堆上。 值types(在CLR的所有实现中)存储在堆栈上。 堆栈分配string会打破各种各样的事情:堆栈只有1MB,你必须打包每个string,招致副本处罚,你不能实习string,内存使用会膨胀,等等…

(编辑:增加了关于值types存储是一个实现细节的澄清,这导致这种情况下,我们有一个types的价值sematics不是从System.ValueTypeinheritance,谢谢本。

它不是一种值types,因为如果它是一个值types,性能(空间和时间!)将是可怕的,并且它的值必须在每次传递给方法时被复制,等等。

它有价值的语义来保持世界的健全。 你能想象如果编码是多么困难

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

设置bfalse ? 想象一下,编码几乎是任何应用程序将是多么困难。

引用types和值types之间的区别基本上是语言devise中的性能折衷。 引用types在构build和销毁以及垃圾收集方面有一些开销,因为它们是在堆上创build的。 另一方面,数值types在方法调用(如果数据大小大于一个指针)上有开销,因为整个对象被复制而不仅仅是一个指针。 因为string可以(通常是)比指针的大小大得多,所以它们被devise为引用types。 另外,正如Servy指出的那样,值types的大小在编译时必须知道,string并不总是这样。

可变性的问题是一个单独的问题。 引用types和值types都可以是可变的,也可以是不可变的。 值types通常是不可变的,因为可变值types的语义可能会令人困惑。

引用types通常是可变的,但如果有意义的话可以devise为不可变的。 string被定义为不可变的,因为它使某些优化成为可能。 例如,如果相同的string文字在同一个程序中出现多次(这很常见),编译器可以重复使用同一个对象。

那么为什么重载“==”来比较string的文本? 因为这是最有用的语义。 如果两个string由文本相等,则由于优化,它们可以是也可以不是相同的对象引用。 所以比较引用是相当无用的,而比较文本几乎总是你想要的。

更一般地讲,string具有所谓的价值语义 。 这是一个比值types更概括的概念,它是C#特定的实现细节。 值types具有值语义,但引用types也可能具有值语义。 当一个types具有值语义,你不能真正的知道底层实现是一个引用types还是值types,所以你可以考虑一个实现细节。

不仅string是不可变的引用types。 多位代表也是如此。 这就是为什么写作是安全的

 protected void OnMyEventHandler() { delegate handler = this.MyEventHandler; if (null != handler) { handler(this, new EventArgs()); } } 

我想string是不可变的,因为这是最安全的方法来处理它们并分配内存。 为什么他们不是价值types? 以前的作者是正确的关于堆栈大小等。我还会补充说,当你在程序中使用相同的常量string时,使得string的引用types允许保存程序集的大小。 如果你定义

 string s1 = "my string"; //some code here string s2 = "my string"; 

机会是,“我的string”常量的两个实例将只在您的程序集中分配一次。

如果你想pipe理像通常引用types的string,把string放在一个新的StringBuilder(strings)中。 或者使用MemoryStreams。

如果你要创build一个库,你期望在你的函数中传递一个巨大的string,可以将一个参数定义为一个StringBuilder或者一个Stream。

此外,string的实现方式(每个平台不同),当你开始拼接在一起。 就像使用StringBuilder 。 它为你分配一个缓冲区,一旦你到达最后,它会为你分配更多的内存,希望如果你做大的连接,性能不会受到阻碍。

也许Jon Skeet可以在这里帮忙?

这主要是一个性能问题。

让stringperformanceLIKE值types有助于编写代码,但是让它成为一种值types会造成巨大的性能下降。

如需深入了解,请阅读.net框架中关于string的精彩文章 。

对于一个老问题,这是一个迟到的答案,但是所有其他的答案都没有提到,那就是.NET在2005年以前没有generics。

string是一个引用types,而不是值types,因为对于Microsoft来说,确保string可以以非常有效的方式存储在非generics集合 (如System.Collection.ArrayList )中至关重要

将值types存储在非generics集合中需要对称为装箱的types对象进行特殊转换。 当CLR放置一个值types时,它把值包装在一个System.Object中并将其存储在托pipe堆中。

从集合中读取值需要被称为拆箱的逆操作。

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

一些答案不正确地声称,该string永远不会被实现为一个值types,因为它的大小是可变的。 实际上,使用小string优化策略很容易将string实现为固定长度的数据结构:string将作为一系列Unicode字符直接存储在内存中,除了大string将被存储为指向外部缓冲区的指针。 两种表示都可以被devise为具有相同的固定长度,即指针的大小。

如果generics从第一天开始存在,那么我认为将string作为值types可能是更好的解决scheme,具有更简单的语义,更好的内存使用和更好的caching局部性。 只包含小string的List <string>可能是单个连续的内存块。

你怎么知道string是一个引用types? 我不确定它是如何实施的。 C#中的string是不可改变的,所以你不必担心这个问题。

实际上,string与值types几乎没有相似之处。 对于初学者来说,并不是所有的值types都是不可变的,你可以改变Int32的值,而且它仍然是堆栈中的相同地址。

string是不可改变的,因为它是一个引用types,与内存pipe理有很大关系。 在string大小更改时创build新对象比在托pipe堆上移动事物效率更高。 我想你是在混合在一起的值/参考types和不可变对象的概念。

至于“==”:像你说的那样,“==”是一个运算符重载,而且它的实现也是一个非常好的理由,使得在使用string时框架更有用。

string不是由string组成的。 我将string视为字符数组[]。 因此,它们位于堆上,因为参考内存位置存储在堆栈中,并指向堆上arrays的内存位置的开始位置。 string的大小在分配之前是不知道的…堆是完美的。

这就是为什么一个string真的是不可变的,因为即使它的大小相同,编译器也不知道这个string,并且不得不分配一个新的数组,并将字符赋给数组中的位置。 如果你认为string是一种语言保护你不必dynamic分配内存的方式(读C就像编程)

冒着又一次神秘的反对票的风险……许多人提到堆栈和内存的价值types和原始types的事实是因为它们必须适合微处理器中的寄存器。 因为在32位系统上eax是32位宽,所以如果指令是比如“pop eax”,那么你不能在栈上压入或popup某些东西。

浮点基元types由80位宽的FPU处理。

这一切都是在OOP语言混淆原始types的定义之前就已经确定了,而且我认为值types是专门为OOP语言创build的一个术语。

用一个非常简单的话来说,任何具有确定大小的值都可以被看作是一个值types。