假装.NET字符串是值类型

Din*_*nah 7 .net string pass-by-reference pass-by-value

在.NET中,字符串是不可变的,并且是引用类型变量.对于较新的.NET开发人员来说,这通常会让他们感到惊讶,因为他们的行为可能会将他们误认为值类型对象.但是,除了StringBuilder用于长连接的实践之外,尤其如此.在循环中,有没有理由在实践中需要知道这种区别?

通过理解关于.NET字符串的价值参考区别与仅假装/误解它们是价值类型,可以帮助或避免哪些现实场景?

Abe*_*bel 17

strings 的设计是故意的,你不应该担心它作为一个程序员.在许多情况下,这意味着如果对字符串的另一个引用存在并且将同时更改(如对象引用所发生的那样),则可以分配,移动,复制,更改字符串而不必考虑太多可能的复杂后果.

方法调用中的字符串参数

(编辑:此部分稍后添加)
当字符串传递给方法时,它们通过引用传递.当它们只在方法体中读取时,没有什么特别的事情发生.但是当它们被更改时,会创建一个副本,并在方法的其余部分中使用临时变量.此过程称为写时复制.

让青少年感到困扰的是,他们已经习惯了对象是引用的事实,并且它们在更改传递参数的方法中被更改.要对字符串执行相同操作,需要使用ref关键字.这实际上允许更改字符串引用并返回到调用函数.如果不这样做,方法体不能更改字符串:

void ChangeBad(string s)      { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }

// in calling method:
string s1 = "hi";
ChangeBad(s1);       // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1);  // s1 changes to "hello world" on return
Run Code Online (Sandbox Code Playgroud)

在StringBuilder上

这种区别很重要,但初学者程序员通常最好不要过多地了解它.使用StringBuilder时,你做了很多串的"建设"是好的,但往往,你的应用程序将有更多的鱼鱼苗和小的性能提升StringBuilder是可以忽略不计.警惕那些告诉你所有字符串操作应该使用StringBuilder完成的程序员.

作为一个非常粗略的经验法则:StringBuilder有一些创建成本,但追加是便宜的.String具有廉价的创建成本,但是连接相对昂贵.转折点大约是400-500个连接,具体取决于大小:之后,StringBuilder变得更有效率.

有关StringBuilder与字符串性能的更多信息

编辑:根据Konrad Rudolph的评论,我添加了这一部分.

如果以前的经验法则让您感到疑惑,请考虑以下略微更详细的解释:

  • 带有许多小字符串附加的StringBuilder很快就会出现字符串连接(30,50个附加),但是在2μs上,甚至100%的性能提升通常可以忽略不计(对于某些罕见的情况是安全的);
  • 带有一些大字符串附加的StringBuilder(80个字符或更大的字符串)仅在数千次,有时是数百次迭代之后才会进行字符串连接,并且差异通常只有几个百分点;
  • 混合字符串操作(替换,插入,子字符串,正则表达式等)通常会使StringBuilder或字符串连接相等;
  • 常量的字符串连接可以通过编译器,CLR或JIT进行优化,它不能用于StringBuilder;
  • 代码通常混合拼接+,StringBuilder.Append,String.Format,ToString和其他字符串操作,在这种情况下使用StringBuilder的是几乎没有有效.

所以,当其高效?在附加了许多小字符串的情况下,例如,将数据序列化到文件中,以及一旦"写入"到StringBuilder就不需要更改"写入"数据.并且在许多方法需要附加内容的情况下,因为StringBuilder是引用类型,并且在更改字符串时会复制它们.

在实习字符串上

一个问题上升 - 不仅是初级程序员 - 当他们尝试进行参考比较并发现有时结果是真的,有时它是假的,在看似相同的情况下.发生了什么?当字符串被编译器实现并添加到全局静态实习字符串池时,两个字符串之间的比较可以指向相同的内存地址.当(参考!)比较两个相等的字符串时,一个实习,一个不实,将产生错误.使用=比较,或者在处理字符串时Equals不要ReferenceEquals使用.

在String.Empty上

同一联盟适合使用时有时会发生的奇怪行为String.Empty:静态String.Empty总是被实现,但是具有赋值的变量则不是.但是,默认情况下,编译器将分配String.Empty并指向相同的内存地址.结果:与之相比,可变字符串变量ReferenceEquals返回true,而您可能会期望为false.

// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";

// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);

// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);
Run Code Online (Sandbox Code Playgroud)

深入

你基本上问过外行人可能会遇到什么情况.我认为我的观点归结为避免,object.ReferenceEquals因为它与字符串一起使用时不可信.原因是当字符串在代码中是常量时使用字符串实习,但并非总是如此.你不能依赖这种行为.虽然String.Empty并且""总是被实习,但是当编译器认为值是可变的时候.不同的优化选项(调试与发布和其他)将产生不同的结果.

需要ReferenceEquals呢?使用对象是有意义的,但是使用字符串则不然.教任何使用字符串的人都要避免使用字符串,除非他们也理解unsafe和固定对象.

性能

当性能是非常重要的,你可以找出串实际上并非一成不变,并且使用StringBuilder不是总是最快的方法.

我在这里使用的很多信息都详细介绍了这篇关于字符串的优秀文章,以及"如何"操作字符串就地(可变字符串).

更新:添加代码示例
更新:添加'深度'部分(希望有人发现这个有用;)
更新:添加了一些链接,添加了关于字符串参数的部分
更新:添加估计何时从字符串切换到stringbuilder
更新:添加了一个额外的部分在Konrad Rudolph的评论之后,在StringBuilder与String的表现