Становится ли StringBuilder неизменным после вызова ToString? - PullRequest
15 голосов
/ 12 ноября 2010

Я отчетливо помню, как в первые дни .NET был вызов ToString для StringBuilder, который использовал для предоставления нового строкового объекта (который должен быть возвращен) внутренний буфер символов, используемый StringBuilder.Таким образом, если вы сконструировали огромную строку с использованием StringBuilder, вызов ToString не должен был ее копировать.

При этом StringBuilder должен был предотвратить любые дополнительные изменения в буфере, поскольку теперь он использовалсянеизменяемая строкаВ результате StringBuilder будет переключаться на «копирование при изменении», когда любая попытка изменения сначала создаст новый буфер, скопирует в него содержимое старого буфера и только затем изменит его.

IЯ думаю, что предполагалось, что StringBuilder будет использован для создания строки, затем преобразован в обычную строку и отброшен.Для меня это кажется разумным предположением.

Теперь вот эта вещь.Я не могу найти упоминания об этом в документации.Но я не уверен, что это когда-либо было задокументировано.

Поэтому я посмотрел на реализацию ToString с использованием Reflector (.NET 4.0), и мне кажется, что он на самом деле копирует строку, а не просто разделяетбуфер:

[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.Я даже нашел упоминание в этой книге .

Мой вопрос: было ли это поведение отброшено?Если так, кто-нибудь знает почему?Это имело смысл для меня ...

Ответы [ 5 ]

5 голосов
/ 12 ноября 2010

Да, это было полностью переработано для .NET 4.0. Теперь он использует веревку, связанный список строителей строк для хранения растущего внутреннего буфера. Это обходной путь для проблемы, когда вы не можете точно угадать начальную емкость и объем текста велик. Это создает много копий неиспользуемого внутреннего буфера, засоряя кучу больших объектов. Этот комментарий из исходного кода, доступный из Справочного источника, имеет отношение:

    // 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;
5 голосов
/ 12 ноября 2010

Да, вы правильно помните.Метод StringBuilder.ToString, используемый для возврата внутреннего буфера в виде строки и пометки его как использованного, чтобы дополнительные изменения в StringBuilder должны были выделить новый буфер.

Поскольку это деталь реализации, этоне упоминается в документации.Вот почему они могут изменить базовую реализацию, не нарушая ничего в определенном поведении класса.

Как видно из опубликованного кода, больше нет единственного внутреннего буфера, вместо этого символы хранятся вчанки, а метод ToString объединяет чанки в строку.

Причиной такого изменения в реализации, вероятно, является то, что они собрали информацию о том, как на самом деле используется класс StringBuilder, и пришли квывод о том, что этот подход дает лучшую производительность, взвешенную между средним и наихудшим случаями.

2 голосов
/ 12 ноября 2010

Вот реализация .NET 1.1 StringBuilder.ToString от Reflector:

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;
}

Насколько я понимаю, он в некоторых случаях возвращает строку без ее копирования. Тем не менее, я не думаю, что StringBuilder становится неизменным. Вместо этого я думаю, что он будет использовать копирование при записи, если вы продолжите запись в StringBuilder.

0 голосов
/ 12 ноября 2010

Я не видел этого раньше, поэтому вот мое предположение: внутреннее хранилище StringBuilder, похоже, больше не является простым string, а набором «кусков». ToString не может вернуть ссылку на эту внутреннюю строку, потому что она больше не существует.

(теперь версии 4.0 StringBuilders веревки ?)

0 голосов
/ 12 ноября 2010

Скорее всего, это просто детали реализации, а не документированное ограничение интерфейса, предоставляемого StringBuilder.ToString. Тот факт, что вы чувствуете себя неуверенно, если это когда-либо было задокументировано, может указывать на то, что это так.

В книгах часто подробно описываются реализации, чтобы показать некоторое понимание того, как что-то использовать, но большинство из них содержат предупреждение о том, что реализация может быть изменена.

Хороший пример того, почему никогда не следует полагаться на детали реализации.

Я подозреваю, что сборщик стал неизменным, это не особенность, а лишь побочный эффект от реализации ToString.

...