一尘不染

为什么.NET String是不可变的?

c#

众所周知,String是不可变的。String不可变以及将StringBuilder类引入可变的原因是什么?


阅读 255

收藏
2020-05-19

共1个答案

一尘不染

  1. 不变类型的实例本质上是线程安全的,因为没有线程可以对其进行修改,因此消除了线程以干扰另一线程的方式对其进行修改的风险(引用本身是另一回事)。
  2. 同样,别名不能产生变化(如果x和y都引用同一个对象,则x的变化必然导致y的变化)这一事实允许对编译器进行大量优化。
  3. 节省内存的优化也是可能的。实习和原子化是最明显的例子,尽管我们可以做同样原理的其他版本。我曾经通过比较不可变的对象并替换对重复项的引用,从而使它们都指向同一个实例,从而节省了大约GB的内存(这很耗时,但是一分钟的额外启动来节省大量内存是在有关情况下获得性能胜利)。对于可变对象,这是无法完成的。
  4. 除非将不可变类型作为方法传递给参数,否则不会有任何副作用,除非它是outref(因为更改了引用,而不是对象)。因此,程序员知道,如果string x = "abc"在方法的开头,并且方法的主体没有变化,则x == "abc"在方法的结尾。
  5. 从概念上讲,语义更像是值类型。特别是,平等是基于国家而不是身份。这意味着"abc" == "ab" + "c"。尽管这不需要不变性,但对这样的字符串的引用在其整个生命周期中始终等于“ abc”(这确实需要不变性),这一事实使得用作保持与先前值相等的关键至关重要,更容易确保正确性of(字符串确实经常被用作键)。
  6. 从概念上讲,不变是更有意义的。如果我们在圣诞节前增加一个月,就不会更改圣诞节,而是在1月下旬确定了一个新的日期。因此,有意义的是Christmas.AddMonths(1)产生一个新的DateTime而不是更改一个可变的。(另一个示例,如果我作为一个可变对象更改了我的姓名,那么更改的是我使用的名称,“ Jon”保持不变,其他Jon将不受影响。
  7. 复制非常简单,只需创建一个克隆即可return this。由于无论如何都无法更改副本,因此假装某些东西是它自己的副本是安全的。
  8. [编辑,我忘了这个]。内部状态可以在对象之间安全地共享。例如,如果您要实现由数组,起始索引和计数支持的列表,那么创建子范围最昂贵的部分就是复制对象。但是,如果它是不可变的,则子范围对象可以引用同一数组,而仅起始索引和计数必须更改,而构造时间将发生 非常 可观的变化。

总而言之,对于没有发生变化的对象来说,保持不变是有很多好处的。主要缺点是需要额外的构造,尽管即使在这里它也常常被夸大了(请记住,在StringBuilder变得比等效串联系列及其固有构造更有效之前,您必须进行几次附加操作)。

如果可变性是对象(希望由永远不会改变其薪水的Employee对象建模)目的的一部分,那将是一个不利条件,尽管有时甚至可以使用(在许多Web和其他无状态环境中)应用程序,执行读取操作的代码与执行更新操作是分开的,并且使用不同的对象可能是很自然的-
我不会使对象成为不可变的,然后强制使用该模式,但是如果我已经拥有该模式,则可以使我的“读取”对象对于性能和正确性保证收益而言是不变的)。

写时复制是中间立场。此处,“真实”类包含对“状态”类的引用。状态类在复制操作上共享,但是如果更改状态,则会创建状态类的新副本。C 比C#更常与C
一起使用,这就是为什么它的std:string拥有不可变类型的一些(但不是全部)优点,同时仍然可变的原因。

2020-05-19