Menu

StringBuilder em C#: Append, Insert, Replace e quando usar

Por que concatenar strings repetidamente em um laço é lento em C#, e como o StringBuilder resolve isso: Append, AppendLine, AppendFormat, Insert, Remove, Replace, capacidade, e quando a concatenação simples ou string.Join é a melhor escolha.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Strings em C# são imutáveis: s += "x" não estende s, ele cria uma string nova com os caracteres antigos mais os novos, e a string antiga vira lixo. Uma vez não é nada. Em um laço que executa milhares de vezes, cada iteração copia tudo o que já foi montado, então o trabalho total cresce com o quadrado do tamanho.

StringBuilder, em System.Text, é um buffer mutável. Append escreve no espaço livre do fim, aumentando o buffer de vez em quando, e ToString() gera uma string no final.

Saída:

Order #1042, total 59.97
Status: shipped
**********
51

Por que concatenar em um laço é lento

Aqui está o mesmo texto montado das duas formas. Os tempos variam de máquina para máquina, então os números abaixo são só um exemplo; o que importa é a diferença, que aumenta à medida que o laço cresce:

Exemplo de saída:

string +=     482 ms, length 188890
StringBuilder 1 ms, length 188890

Com 20.000 acréscimos, a versão com += copia quase 2 bilhões de caracteres no total. O StringBuilder copia cada caractere mais ou menos duas vezes, uma quando ele é acrescentado e outra no ToString(), e é por isso que fica perto de um milissegundo.

Append, AppendLine e AppendFormat

Append tem sobrecargas para todo tipo embutido e retorna o mesmo builder, então as chamadas podem ser encadeadas. AppendLine adiciona uma quebra de linha depois do argumento. AppendFormat recebe uma string de formato composta, como string.Format:

Saída:

RECEIPT
-------
Espresso        2.40
Croissant       3.10
Orange juice    4.50
TOTAL          10.00

Acrescentar uma string interpolada (sb.Append($"...")) não tem problema e se lê bem. No .NET 6 em diante, o compilador até escreve os pedaços direto no builder, sem criar a string intermediária.

AppendLine usa Environment.NewLine: \n no Linux e no macOS, \r\n no Windows. Quando um formato de arquivo exige um final de linha específico, acrescente-o você mesmo.

Insert, Remove, Replace e o indexador

Ao contrário de uma string, um StringBuilder pode ser editado no lugar:

Saída:

Hello, World
Hello, Maya
Maya
maya
maya!
[] length 0

Definir Length com um valor menor trunca; é a forma mais barata de remover um separador sobrando no fim. Clear() define o tamanho como 0 e mantém o buffer alocado, então um builder pode ser reutilizado entre as iterações de um laço externo.

O indexador é rápido para acréscimos recentes, mas pode ser lento no acesso aleatório a um builder muito grande, porque o conteúdo é guardado em blocos encadeados. Se você precisa ler caracteres, chame ToString() uma vez e acesse a string por índice.

O problema do separador sobrando

Um uso clássico é montar uma lista separada por vírgulas. Acrescentar um separador depois de cada item deixa um a mais no fim:

Saída:

csharp, dotnet, linq
csharp, dotnet, linq

Quando os itens já estão em uma coleção, string.Join é mais curto e pelo menos tão rápido. Deixe o StringBuilder para textos cuja forma depende de lógica: seções condicionais, laços aninhados, formatação misturada.

Capacidade

Um StringBuilder começa com espaço para 16 caracteres. Quando o espaço acaba, ele encadeia um novo bloco de armazenamento do tamanho de tudo o que já guarda, até 8.000 caracteres por bloco, então a capacidade dobra enquanto ele é pequeno e depois cresce em passos de 8.000 caracteres. Crescer nunca copia o texto já guardado. Se você sabe mais ou menos o tamanho do resultado, passe a capacidade ao construtor e o builder nunca precisa crescer:

Saída:

12000
10000
True
True

Definir o tamanho antecipadamente é uma otimização, não uma exigência. O crescimento aloca um bloco novo, mas nunca move o texto existente, então o padrão serve, a menos que você esteja montando muitas strings grandes em um caminho crítico.

Quando não usar StringBuilder

StringBuilder não é um substituto mais rápido para todo +. O compilador já transforma a + b + c + d em uma única expressão em uma chamada a string.Concat, que calcula o tamanho final e copia cada pedaço uma vez, o mesmo trabalho que um builder faria com mais código. Use strings simples quando:

  • O número de pedaços é pequeno e fixo: $"{first} {last} ({age})".
  • Os pedaços estão em uma coleção e precisam de um separador: string.Join(", ", items).
  • Os pedaços não precisam de separador: string.Concat(parts).
  • Você concatena poucas vezes, fora de qualquer laço.

Use StringBuilder quando você acrescenta dentro de um laço, acrescenta um número de vezes que não sabe de antemão, ou edita o texto (inserir, substituir, remover) enquanto o monta. Converter de um lado para o outro (sb.ToString() dentro do laço) joga a vantagem fora; chame-o uma vez no final.

Streams são a outra alternativa: para produzir um arquivo grande, escreva as linhas em um StreamWriter à medida que avança, em vez de montar o conteúdo inteiro na memória. Veja arquivos.

Perguntas frequentes

O que é StringBuilder em C#?

System.Text.StringBuilder é um buffer mutável de caracteres para montar uma string em muitos passos. Append adiciona ao fim do buffer em vez de criar uma string nova a cada vez, e ToString() produz a string final uma única vez. É a ferramenta padrão para montar texto em um laço.

Quando usar StringBuilder em vez de concatenar strings?

Quando você acrescenta dentro de um laço ou um número desconhecido de vezes, como ao montar um relatório, um arquivo CSV ou HTML a partir de uma coleção. Para um punhado fixo de pedaços (a + b + c, ou uma string interpolada), a concatenação simples é tão rápida quanto e mais fácil de ler, porque o compilador já a junta em uma única chamada a string.Concat.

Como remover o último caractere de um StringBuilder?

Reduza o Length: if (sb.Length > 0) sb.Length--; descarta o último caractere sem copiar nada. sb.Remove(sb.Length - 1, 1) faz o mesmo. É a forma usual de tirar uma vírgula sobrando no fim, embora string.Join evite o separador sobrando desde o início.

StringBuilder é thread safe?

Não. Chamar Append no mesmo StringBuilder a partir de várias threads ao mesmo tempo pode corromper o conteúdo ou lançar uma exceção. Dê a cada thread o seu próprio builder e combine os resultados, ou proteja o compartilhado com um lock.

O que o AppendLine adiciona em C#?

O texto seguido de Environment.NewLine, que é \r\n no Windows e \n no Linux e no macOS. Se a saída precisa usar um final de linha específico, como \n em um formato de arquivo ou \r\n em um cabeçalho HTTP, acrescente-o explicitamente com Append("\n").

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR