当前位置:  开发笔记 > 编程语言 > 正文

在C#中,为什么String是一个行为类似值的引用类型?

如何解决《在C#中,为什么String是一个行为类似值的引用类型?》经验,为你挑选了7个好方法。

String是一种引用类型,即使它具有值类型的大多数特性,例如是不可变的并且具有==重载以比较文本而不是确保它们引用相同的对象.

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



1> codekaizen..:

字符串不是值类型,因为它们可能很大,并且需要存储在堆上.存储在堆栈上的值类型(在CLR的所有实现中).堆栈分配字符串会破坏各种各样的东西:32位只有1MB,64位只有4MB,你必须打包每个字符串,导致复制惩罚,你不能实习字符串和内存使用会气球等...

(编辑:添加了关于值类型存储作为实现细节的说明,这导致了这种情况,我们有一个类型的值没有继承自System.ValueType的值.感谢Ben.)


我在这里挑剔,但只是因为它让我有机会链接到与问题相关的博客文章:值类型不一定存储在堆栈中.在ms.net中最常见,但CLI规范并未指定.值和引用类型之间的主要区别在于引用类型遵循按值复制的语义.请参阅http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx和http://blogs.msdn.com/ericlippert/archive/2009 /05/04/the-stack-is-an-implementation-detail-part-two.aspx
@Qwertie:`String`不是可变大小.当你添加它时,你实际上是在创建另一个`String`对象,为它分配新的内存.
@codekaizen Qwertie是对的,但我认为措辞令人困惑.一个字符串可能与另一个字符串的大小不同,因此,与真值类型不同,编译器无法事先知道要分配多少空间来存储字符串值.例如,`Int32`总是4个字节,因此编译器在您定义字符串变量时分配4个字节.编译器遇到`int`变量时应该分配多少内存(如果它是值类型)?了解当时尚未分配该值.
也就是说,理论上,字符串可以是值类型(结构),但"值"只不过是对字符串的引用..NET设计者自然决定切断中间人(结构处理在.NET 1.0中效率低下,并且遵循Java是很自然的,其中字符串已被定义为引用,而不是原始类型.另外,如果字符串是一个值类型然后将其转换为对象将需要它被装箱,这是一种不必要的低效率.
对不起,我的评论中有一个错字,我现在无法修复; 应该是....例如,`Int32`总是4个字节,因此编译器在你定义`int`变量时分配4个字节.编译器遇到`string`变量时应该分配多少内存(如果它是值类型)?了解当时尚未分配该值.

2> jason..:

它不是一个值类型,因为如果它是一个值类型,性能(空间和时间!)将是可怕的,并且每次传递给方法等返回它的值时都必须复制它的值.

它具有保持世界理智的价值语义.你能想象编码是多么困难吗?

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

设置bfalse?想象一下,编写任何应用程序的难度都很大.


Java并不以简洁而着称.
多数民众赞成如何工作,你必须使用s.equals(t).
@Juri:实际上我认为检查引用是不可取的,因为有时`new String("foo");`和另一个`new String("foo")`可以在同一个引用中进行评估,哪种不是什么你会期望一个`new`操作符.(或者你能告诉我一个我想比较参考文献的情况吗?)
@Matt:确切地说.当我切换到C#时,这有点令人困惑,因为我总是使用(有时仍然).equals(..)用于比较字符串,而我的队友只使用"==".我从来没有理解为什么他们没有留下"=="来比较引用,尽管如果你认为,90%的时间你可能想要比较内容而不是字符串的引用.

3> JacquesB..:

引用类型和值类型之间的区别基本上是语言设计中的性能折衷.引用类型在构造和销毁以及垃圾收集方面有一些开销,因为它们是在堆上创建的.另一方面,值类型在方法调用上有开销(如果数据大小大于指针),因为整个对象被复制而不仅仅是指针.因为字符串可以(并且通常是)远大于指针的大小,所以它们被设计为引用类型.此外,正如Servy所指出的那样,值类型的大小必须在编译时知道,而字符串并不总是如此.

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

引用类型通常是可变的,但如果有意义,可以设计为不可变的.字符串被定义为不可变的,因为它使某些优化成为可能.例如,如果在同一程序中多次出现相同的字符串文字(这很常见),编译器可以重用相同的对象.

那么为什么"=="重载以按文本比较字符串呢?因为它是最有用的语义.如果两个字符串相等,则由于优化,它们可能是也可能不是同一个对象引用.所以比较参考文献是没有用的,而比较文本几乎总是你想要的.

更一般地说,字符串具有所谓的价值语义.这是一个比值类型更通用的概念,它是C#特定的实现细节.值类型具有值语义,但引用类型也可能具有值语义.当类型具有值语义时,您无法确定底层实现是引用类型还是值类型,因此您可以考虑实现细节.


@Sevy:字符串*的大小是*常量.

4> ZunTzu..:

这是对旧问题的一个迟到的答案,但所有其他答案都忽略了这一点,即.NET在2005年的.NET 2.0之前没有泛型.

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

在非泛型集合中存储值类型需要特殊转换object为称为装箱的类型.当CLR选择一个值类型时,它将值包装在a中System.Object并将其存储在托管堆上.

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

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

一些答案声称错误,string因为它的大小是可变的,所以它永远不会被实现为值类型.实际上,使用小字符串优化策略将字符串实现为固定长度的数据结构很容易:字符串将作为Unicode字符序列直接存储在内存中,除了将作为指向外部缓冲区的指针存储的大字符串.两种表示都可以设计成具有相同的固定长度,即指针的大小.

如果泛型从第一天开始就存在,我想将字符串作为值类型可能是一个更好的解决方案,具有更简单的语义,更好的内存使用和更好的缓存局部性.List仅包含小字符串的A 可能是单个连续的内存块.



5> Bogdan_Ch..:

不仅字符串是不可变的引用类型. 多播代表也是如此. 这就是写作安全的原因

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

我认为字符串是不可变的,因为这是使用它们并分配内存的最安全的方法.为什么他们不是价值类型?以前的作者对堆栈大小等是正确的.我还要补充说,在程序中使用相同的常量字符串时,使字符串成为引用类型允许节省程序集大小.如果你定义

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

有可能"my string"常量的两个实例只会在程序集中分配一次.

如果您想像通常的引用类型一样管理字符串,请将字符串放在新的StringBuilder(字符串s)中.或者使用MemoryStreams.

如果要创建一个库,您希望在函数中传递一个巨大的字符串,可以将参数定义为StringBuilder或Stream.


最后一点:如果你试图*传递一个大字符串(因为它实际上是作为一个字符串实现),StringBuilder没有帮助 - StringBuilder对**多次操作**一个字符串非常有用.

6> Chris..:

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

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



7> Denis Trolle..:

这主要是性能问题.

让字符串表现为LIKE值类型有助于编写代码,但将其作为值类型会产生巨大的性能损失.

要深入了解一下,请查看.net框架中有关字符串的精彩文章.

推荐阅读
Chloemw
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有