Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
.net 调用ToString后StringBuilder是否变得不可变?_.net_Immutability_Tostring_Stringbuilder_Copy On Write - Fatal编程技术网

.net 调用ToString后StringBuilder是否变得不可变?

.net 调用ToString后StringBuilder是否变得不可变?,.net,immutability,tostring,stringbuilder,copy-on-write,.net,Immutability,Tostring,Stringbuilder,Copy On Write,我清楚地记得,在.NET的早期,在StringBuilder上调用ToString用于为新的string对象(要返回)提供StringBuilder使用的内部字符缓冲区。这样,如果您使用StringBuilder构建了一个巨大的字符串,那么调用ToString就不必复制它 在这样做时,StringBuilder必须防止对缓冲区进行任何额外的更改,因为它现在被一个不可变的字符串使用。因此,StringBuilder将切换到“更改时复制”,任何尝试的更改都将首先创建一个新缓冲区,将旧缓冲区的内容复制

我清楚地记得,在.NET的早期,在StringBuilder上调用ToString用于为新的string对象(要返回)提供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;
}
现在,正如我之前提到的,我清楚地记得在早期的if.NET阅读时就是这样。我甚至在这篇文章中提到了


我的问题是,这种行为是不是已经放弃了?如果是这样,有人知道原因吗?这对我来说非常有意义…

这很可能只是一个实现细节,而不是
StringBuilder.ToString
提供的接口上的文档约束。事实上,你不确定它是否被记录下来,这可能表明情况就是这样

书籍通常会详细介绍实现,以展示对如何使用某些东西的一些见解,但大多数书籍都有一个警告,即实现可能会发生更改

这是一个很好的例子,说明了为什么永远不应该依赖于实现细节


我怀疑这并不是让构建器变得不可变的特性,而仅仅是实现
ToString
的副作用。我以前从未见过这一点,因此我猜测:
StringBuilder
的内部存储不再是一个简单的
string
,而是一组“chunk”
ToString
无法返回对此内部字符串的引用,因为它已不存在


(现在是4.0版StringBuilders吗?

是的,您记得正确。
StringBuilder.ToString
方法用于将内部缓冲区作为字符串返回,并将其标记为已使用,以便对
StringBuilder
的其他更改必须分配新缓冲区

由于这是一个实现细节,文档中没有提到。这就是为什么他们可以在不破坏类的已定义行为的情况下更改底层实现

正如您从发布的代码中看到的,不再有单一的内部缓冲区,而是将字符存储在块中,
ToString
方法将这些块组合成一个字符串


实现中出现这种变化的原因可能是他们收集了有关
StringBuilder
类实际使用方式的信息,并得出结论,这种方法在平均情况和最坏情况之间进行权衡,可以提供更好的性能。

以下是Reflector中
StringBuilder.ToString
的.NET 1.1实现:

public override string ToString()
{
    string stringValue = this.m_StringValue;
    int currentThread = this.m_currentThread;
    if ((currentThread != 0) && (currentThread != InternalGetCurrentThread()))
    {
        return string.InternalCopy(stringValue);
    }
    if ((2 * stringValue.Length) < stringValue.ArrayLength)
    {
        return string.InternalCopy(stringValue);
    }
    stringValue.ClearPostNullChar();
    this.m_currentThread = 0;
    return stringValue;
}
public重写字符串ToString()
{
string stringValue=此.m_stringValue;
int currentThread=this.m_currentThread;
如果((currentThread!=0)&&(currentThread!=InternalGetCurrentThread())
{
返回string.InternalCopy(stringValue);
}
if((2*stringValue.Length)

据我所知,在某些情况下,它将返回字符串而不复制它。然而,我并不认为
StringBuilder
是不变的。相反,我认为如果您继续向
StringBuilder

写入,它将使用写时复制。是的,它已经为.NET 4.0完全重新设计。它现在使用一个rope,一个字符串生成器的链接列表来存储不断增长的内部缓冲区。这是一个解决问题的办法,当你猜不出初始容量,并且文本量很大时。这会创建大量已废弃内部缓冲区的副本,从而阻塞大型对象堆。参考源代码中提供的源代码注释与以下内容相关:

    // We want to keep chunk arrays out of large object heap (< 85K bytes ~ 40K chars) to be sure.
    // Making the maximum chunk size big means less allocation code called, but also more waste 
    // in unused characters and slower inserts / replaces (since you do need to slide characters over
    // within a buffer).
    internal const int MaxChunkSize = 8000;
//我们希望确保块数组不在大型对象堆(<85K字节~40K字符)中。
//使最大块大小变大意味着调用的分配代码更少,但也意味着更多的浪费
//在未使用的字符和较慢的插入/替换中(因为您确实需要滑动字符
//在缓冲区内)。
内部常量int MaxChunkSize=8000;

谢谢你,杰夫。我知道这是一个实现细节,我不以任何方式依赖它。我很好奇的是,为什么实现会改变,因为它看起来仍然很有意义。它看起来更像是一个区块链,而不是一个区块树。很有趣。字符串存储为一系列字符[]s。但行“chunkPrevious=chunkPrevious.m_chunkPrevious;”不意味着这些数组存储在单独的StringBuilder实例中,作为链接列表相关,内部存储在我们参考的StringBuilder实例中吗?谢谢+1为了添加.NET 1.1实现,StringBuilder在开始使用ropes之前很久就在其ToString()方法中返回了一个新字符串,当时Microsoft意识到,任何在可变时暴露于外部进行非线程保护写访问的对象都必须永远假定为可变的(因为无法知道某个线程是否