Como a class StringBuilder é implementada? Cria internamente novos objects string cada vez que acrescentamos?

Como a class StringBuilder é implementada? Cria internamente novos objects string cada vez que acrescentamos?

No .NET 2.0, ele usa a class String internamente. String é apenas imutável fora do namespace System , então StringBuilder pode fazer isso.

No .NET 4.0 String foi alterado para usar char[] .

Em 2.0 StringBuilder ficou assim

 public sealed class StringBuilder : ISerializable { // Fields private const string CapacityField = "Capacity"; internal const int DefaultCapacity = 0x10; internal IntPtr m_currentThread; internal int m_MaxCapacity; internal volatile string m_StringValue; // HERE ---------------------- private const string MaxCapacityField = "m_MaxCapacity"; private const string StringValueField = "m_StringValue"; private const string ThreadIDField = "m_currentThread"; 

Mas na versão 4.0 é assim:

 public sealed class StringBuilder : ISerializable { // Fields private const string CapacityField = "Capacity"; internal const int DefaultCapacity = 0x10; internal char[] m_ChunkChars; // HERE -------------------------------- internal int m_ChunkLength; internal int m_ChunkOffset; internal StringBuilder m_ChunkPrevious; internal int m_MaxCapacity; private const string MaxCapacityField = "m_MaxCapacity"; internal const int MaxChunkSize = 0x1f40; private const string StringValueField = "m_StringValue"; private const string ThreadIDField = "m_currentThread"; 

Então, evidentemente, foi alterado de usar uma string para usar um char[] .

EDIT: resposta atualizada para refletir as alterações no .NET 4 (que acabei de descobrir).

A resposta aceita erra a marca por uma milha. A mudança significativa para o StringBuilder em 4.0 não é a mudança de uma string não segura para char[] – é o fato de que StringBuilder é atualmente uma linked list de instâncias de StringBuilder .


A razão para essa mudança deve ser óbvia: agora não há necessidade de realocar o buffer (uma operação cara, pois, além de alocar mais memory, você também precisa copiar todo o conteúdo do buffer antigo para o novo) .

Isso significa que chamar ToString() é agora um pouco mais lento, já que a string final precisa ser calculada, mas fazer um grande número de operações Append() agora é significativamente mais rápido. Isso se encheckbox no caso de uso típico de StringBuilder : muitas chamadas para Append() , seguidas por uma única chamada para ToString() .


Você pode encontrar benchmarks aqui . A conclusão? O novo StringBuilder linked list usa marginalmente mais memory, mas é significativamente mais rápido para o caso de uso típico.

Na verdade não – ele usa buffer de caractere interno. Somente quando a capacidade do buffer se esgotar, ele alocará o novo buffer. A operação Append será simplesmente adicionada a esse buffer, o object string será criado quando o método ToString () for chamado – daí em diante, é aconselhável para muitas concatenações de strings, já que cada concatenação de string tradicional criaria uma nova string. Você também pode especificar a capacidade inicial para o construtor de string se tiver uma ideia aproximada sobre isso para evitar várias alocações.

Edit : As pessoas estão apontando que o meu entendimento está errado. Por favor, ignore a resposta (eu prefiro não excluí-lo – será uma prova da minha ignorância 🙂

Fiz uma pequena amostra para demonstrar como o StringBuilder funciona no .NET 4. O contrato é

 public interface ISimpleStringBuilder { ISimpleStringBuilder Append(string value); ISimpleStringBuilder Clear(); int Lenght { get; } int Capacity { get; } } 

E esta é uma implementação muito básica

 public class SimpleStringBuilder : ISimpleStringBuilder { public const int DefaultCapacity = 32; private char[] _internalBuffer; public int Lenght { get; private set; } public int Capacity { get; private set; } public SimpleStringBuilder(int capacity) { Capacity = capacity; _internalBuffer = new char[capacity]; Lenght = 0; } public SimpleStringBuilder() : this(DefaultCapacity) { } public ISimpleStringBuilder Append(string value) { char[] data = value.ToCharArray(); //check if space is available for additional data InternalEnsureCapacity(data.Length); foreach (char t in data) { _internalBuffer[Lenght] = t; Lenght++; } return this; } public ISimpleStringBuilder Clear() { _internalBuffer = new char[Capacity]; Lenght = 0; return this; } public override string ToString() { //use only non-null ('\0') characters var tmp = new char[Lenght]; for (int i = 0; i < Lenght; i++) { tmp[i] = _internalBuffer[i]; } return new string(tmp); } private void InternalExpandBuffer() { //double capacity by default Capacity *= 2; //copy to new array var tmpBuffer = new char[Capacity]; for (int i = 0; i < _internalBuffer.Length; i++) { char c = _internalBuffer[i]; tmpBuffer[i] = c; } _internalBuffer = tmpBuffer; } private void InternalEnsureCapacity(int additionalLenghtRequired) { while (Lenght + additionalLenghtRequired > Capacity) { //not enough space in the current buffer //double capacity InternalExpandBuffer(); } } } 

Esse código não é thread-safe, não faz qualquer validação de input e não está usando a magia interna (insegura) de System.String. No entanto, demonstra a ideia por detrás da class StringBuilder.

Alguns testes unitários e código de amostra completo podem ser encontrados no github .

Se eu olhar para o .NET Reflector no .NET 2, então eu vou encontrar isso:

 public StringBuilder Append(string value) { if (value != null) { string stringValue = this.m_StringValue; IntPtr currentThread = Thread.InternalGetCurrentThread(); if (this.m_currentThread != currentThread) { stringValue = string.GetStringForStringBuilder(stringValue, stringValue.Capacity); } int length = stringValue.Length; int requiredLength = length + value.Length; if (this.NeedsAllocation(stringValue, requiredLength)) { string newString = this.GetNewString(stringValue, requiredLength); newString.AppendInPlace(value, length); this.ReplaceString(currentThread, newString); } else { stringValue.AppendInPlace(value, length); this.ReplaceString(currentThread, stringValue); } } return this; } 

Então, é uma instância de string mutada …

EDIT Exceto no .NET 4 é um char[]

Se você quiser ver uma das implementações possíveis (que é semelhante ao fornecido com a implementação da microsoft até v3.5), você pode ver a fonte do Mono no github.