我清楚地记得在 .NET 的早期,在 StringBuilder 上调用 ToString 用于提供新的字符串对象(要返回)以及 StringBuilder 使用的内部字符缓冲区。这样,如果您使用 StringBuilder 构造了一个巨大的字符串,则调用 ToString 不必复制它。
在这样做时,StringBuilder 必须防止对缓冲区进行任何其他更改,因为它现在由不可变字符串使用。因此,StringBuilder 将切换到“更改时复制”,其中任何尝试的更改都会首先创建一个新缓冲区,将旧缓冲区的内容复制到它,然后才更改它。
我认为假设是 StringBuilder 将用于构造一个字符串,然后转换为常规字符串并丢弃。对我来说似乎是一个合理的假设。
现在事情来了。我在文档中找不到任何提及。但我不确定它是否被记录在案。
因此,我使用 Reflector (.NET 4.0) 查看了 ToString 的实现,在我看来,它实际上是复制字符串,而不仅仅是共享缓冲区:
[SecuritySafeCritical]
public override unsafe string ToString()
{
string str = string.FastAllocateString(this.Length);
StringBuilder chunkPrevious = this;
fixed (char* str2 = ((char*) str))
{
char* chPtr = str2;
do
{
if (chunkPrevious.m_ChunkLength > 0)
{
char[] chunkChars = chunkPrevious.m_ChunkChars;
int chunkOffset = chunkPrevious.m_ChunkOffset;
int chunkLength = chunkPrevious.m_ChunkLength;
if ((((ulong) (chunkLength + chunkOffset)) > str.Length) || (chunkLength > chunkChars.Length))
{
throw new ArgumentOutOfRangeException("chunkLength", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
fixed (char* chRef = chunkChars)
{
string.wstrcpy(chPtr + chunkOffset, chRef, chunkLength);
}
}
chunkPrevious = chunkPrevious.m_ChunkPrevious;
}
while (chunkPrevious != null);
}
return str;
}
现在,正如我之前提到的,我清楚地记得读过 .NET 早期的情况。我什至在此 book 中发现了一个提及.
我的问题是,这种行为被放弃了吗?如果是这样,有人知道为什么吗?这对我来说很有意义......
最佳答案
是的,你没记错。 StringBuilder.ToString
方法用于将内部缓冲区作为字符串返回,并将其标记为已使用,以便对 StringBuilder
进行其他更改不得不分配一个新的缓冲区。
由于这是一个实现细节,文档中没有提及。这就是为什么他们可以在不破坏类的定义行为的情况下更改底层实现。
正如您从发布的代码中看到的,不再有单个内部缓冲区,而是将字符存储在块中,并且 ToString
方法将块放在一起成为一个字符串。
实现中发生这种变化的原因很可能是他们收集了有关 StringBuilder
的信息。实际使用了类,并得出结论,这种方法在平均和最坏情况之间权衡时提供了更好的性能。
关于.net - 调用 ToString 后,StringBuilder 是否变得不可变?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4166155/