小编典典

为什么 .NET 字符串是不可变的?

all

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


阅读 82

收藏
2022-07-13

共1个答案

小编典典

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

总而言之,对于没有经历变化作为其目的的一部分的对象,不可变可能有很多优点。主要缺点是需要额外的构造,尽管即使在这里它也经常被夸大了(请记住,在
StringBuilder 变得比等价的串联系列更有效之前,您必须进行几次附加,因为它们具有固有的构造)。

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

写时复制是一个中间立场。这里“真实”类包含对“状态”类的引用。状态类在复制操作时共享,但如果您更改状态,则会创建状态类的新副本。与 C# 相比,这更常用于
C++,这就是为什么它的 std:string 享有不可变类型的一些(但不是全部)优点,同时保持可变。

2022-07-13