String, System.String e String Builder no C#
String e StringBuilder são tipos que todo desenvolvedor C# usa, mas que ainda geram dúvidas: qual a diferença entre String e string, e quando usar o StringBuilder? Neste artigo, você vai entender para que serve cada um e como escolher o tipo certo para cada cenário.
O que é uma string no C#
Antes de comparar string e StringBuilder, vale entender o que é uma string.
Uma string no C# é uma cadeia de caracteres que representa um texto. Internamente, o .NET armazena esse texto como uma sequência somente leitura de objetos Char, codificados em UTF-16.
Apesar de se comportar como um valor no dia a dia, a string é um tipo de referência. A documentação oficial sobre strings explica que os operadores == e != comparam o conteúdo, e não a referência, o que torna essa diferença quase invisível.
Uma string pode armazenar aproximadamente 1 bilhão de caracteres, o que corresponde a cerca de 2 GB de memória. Na prática, você raramente chega perto desse limite.
Essa base ajuda a entender por que string e StringBuilder existem lado a lado na plataforma.
Como declarar e inicializar strings
Podemos inicializar uma string de várias formas. O exemplo abaixo mostra as mais comuns, incluindo duas que não compilam.
// Declara uma string sem valor inicial
// Ler essa variável antes de atribuir um valor gera o erro CS0165
string text1;
// Cria uma string informando um valor inicial
string text2 = "value";
// Cria uma string vazia
string text3 = string.Empty;
// ERRO CS0818: variáveis com var precisam ser inicializadas
// var text4;
// Cria uma string informando um valor inicial
var text5 = "value";
// Cria uma string vazia
var text6 = string.Empty;
// Declara uma string que aceita nulo
string? text7 = null;
No exemplo, cada variável tem um nome diferente, porque o C# não permite declarar duas variáveis com o mesmo nome no mesmo escopo.
Strings nulas e string.Empty
Para indicar que uma string pode ser nula, usamos o ? após o tipo. Esse recurso se chama nullable reference types e chegou no C# 8.
Desde o .NET 6, os novos projetos já vêm com esse recurso ativado. Assim, atribuir null a uma string sem ? gera um aviso do compilador, como o CS8600.
string? optionalText = null; // Correto: o tipo aceita nulo
string requiredText = string.Empty; // Correto: valor vazio, nunca nulo
Se quiser se aprofundar nesse assunto, o artigo sobre nullable types no .NET explica a diferença entre int? e string?.
Já o string.Empty representa uma string vazia, com tamanho zero. Ele tem o mesmo resultado que "" e não influencia na performance.
A diferença é que string.Empty é um campo static readonly, e não uma constante. Por isso, ele não funciona onde o compilador exige constantes, como em case de um switch, parâmetros opcionais e argumentos de atributos.
// ERRO: o valor padrão de um parâmetro precisa ser uma constante
// void Greet(string name = string.Empty) { }
// Correto: "" é uma constante
void Greet(string name = "") { }
Para verificar se uma string é nula ou vazia, use string.IsNullOrEmpty. Se espaços em branco também contam como vazio, prefira string.IsNullOrWhiteSpace.
Comparando strings
Como vimos, o operador == compara o conteúdo de duas strings. Contudo, essa comparação diferencia maiúsculas de minúsculas.
Quando a diferença de caixa não importa, use o método string.Equals com um valor de StringComparison. Assim, a intenção fica explícita no código.
var first = "balta";
var second = "BALTA";
Console.WriteLine(first == second); // False
Console.WriteLine(string.Equals(first, second, StringComparison.OrdinalIgnoreCase)); // True
Para identificadores, chaves e nomes técnicos, prefira as comparações ordinais. Já para textos exibidos ao usuário, considere as comparações que respeitam a cultura.
Qual a diferença entre String e string
Tá bem, mas qual a diferença entre string e String? Por que um é minúsculo e o outro começa com letra maiúscula?
string myText = "andre";
String otherValue = "some value";
Embora string, toda minúscula, seja disparadamente a forma mais usada, o tipo real no .NET é o System.String. Ou seja, String com S maiúsculo é o nome da classe.
Todo tipo no .NET herda, direta ou indiretamente, de System.Object. Além disso, os tipos fundamentais ficam no namespace System, e com as strings não é diferente.
O mesmo acontece com outros tipos. Por exemplo, short é um apelido para Int16, int para Int32 e long para Int64.
Por convenção, toda classe ou estrutura no .NET começa com letra maiúscula. Então, tipos que usamos tanto, como short, int, string, bool, float e double, não passam de alias, ou apelidos, para os tipos reais da plataforma.
Na prática, o que isso muda
Nada! Pode continuar usando seu string normalmente, porque para o compilador é tudo a mesma coisa.
Portanto, essa dúvida não tem relação com a escolha entre string e StringBuilder, que envolve comportamento e performance.
Segundo a documentação, os dois nomes geram exatamente o mesmo código intermediário (IL). Portanto, a escolha não afeta performance nem comportamento, é apenas algo estético.
Console.WriteLine(typeof(string) == typeof(String)); // True
A própria Microsoft recomenda usar a palavra-chave string. Ela segue o padrão dos outros tipos built-in e funciona mesmo sem o using System;.
Particularmente, acredito que muitos desses alias vieram de linguagens com sintaxe parecida com a do C, como C++ e Java, e foram mantidos por harmonia. Também tenho que concordar que dá para contar nos dedos as vezes que usei String em vez de string.
Concatenação com string e StringBuilder
Para entender o papel do StringBuilder, precisamos primeiro entender uma característica fundamental da string: ela é imutável. Depois de criada, uma string nunca muda de conteúdo.
Métodos como Replace, Trim e ToUpper não alteram a string original. Em vez disso, eles devolvem uma nova string com o resultado.
É comum ver a concatenação de strings com o operador de atribuição +=. Em textos pequenos, isso não causa muito efeito negativo, mas em textos longos pode se tornar um grande vilão.
var myText = "Hello world";
myText += " here goes one more";
myText += " and here another piece of the string";
A cada concatenação feita desse modo, o .NET cria uma nova string com o conteúdo acumulado das anteriores. A string antiga fica sem referência e espera o garbage collector.
Agora imagine esse cenário dentro de um laço com milhares de repetições. O custo de memória e processamento cresce rapidamente, e é aí que a escolha entre string e StringBuilder faz diferença.
O artigo sobre strings imutáveis em C# aprofunda esse comportamento e suas vantagens.
O que é o StringBuilder e como ele difere da string
Outro item que causa bastante confusão é o StringBuilder. Ele age de forma bem diferente de string e String, porque sua finalidade é montar textos, ou seja, juntar pedaços em um texto maior.
Como o nome já diz, ele é um construtor de strings, feito sob medida para a concatenação de textos. A classe fica no namespace System.Text e, ao contrário da string, é mutável.
using System.Text;
var myText = new StringBuilder();
myText.Append("Hello world");
myText.Append(" here goes one more");
myText.Append(" and here another piece of the string");
Console.WriteLine(myText.ToString());
No exemplo acima, temos um código muito mais otimizado, porque o StringBuilder monta o texto em um único buffer interno. Assim, ele não cria uma nova string a cada Append.
Também notamos que, por se tratar de um objeto, precisamos do método ToString() para obter o texto final. Só nesse momento o .NET cria a string com o conteúdo completo.
Principais métodos do StringBuilder
Além do Append, a classe oferece métodos para inserir, remover e substituir trechos. A maioria deles devolve a própria instância, o que permite encadear chamadas.
var builder = new StringBuilder("Hello");
builder.Append(", world") // Adiciona ao fim
.AppendLine("!") // Adiciona e quebra a linha
.Insert(0, ">> ") // Insere no início
.Replace("world", "C#"); // Substitui um trecho
Console.WriteLine(builder.ToString()); // >> Hello, C#!
Console.WriteLine(builder.Length); // Quantidade de caracteres atual
builder.Clear(); // Limpa o conteúdo para reaproveitar a instância
O método Clear é útil quando você precisa montar vários textos em sequência. Dessa forma, você reaproveita o mesmo objeto em vez de criar um novo.
Capacidade inicial do StringBuilder
Internamente, o StringBuilder reserva um espaço chamado capacidade. Quando o texto ultrapassa esse espaço, a classe aloca mais memória automaticamente.
Se você já sabe o tamanho aproximado do texto final, pode informar a capacidade no construtor. Assim, você reduz o número de realocações.
// Reserva espaço para cerca de 1024 caracteres desde o início
var builder = new StringBuilder(capacity: 1024);
Quando usar string e StringBuilder
A regra prática é simples. Use string para textos fixos e para poucas concatenações, e use StringBuilder quando o número de partes for grande ou desconhecido.
A documentação da Microsoft segue a mesma linha. Para um conjunto pequeno e fixo de valores, o operador + e a interpolação deixam o código mais claro.
Inclusive, quando você concatena vários valores em uma única expressão, como a + b + c, o compilador transforma tudo em uma só chamada a string.Concat. Portanto, o problema real aparece nos laços, quando a concatenação se repete muitas vezes.
// Ruim: cria uma nova string a cada volta do laço
var report = string.Empty;
for (var i = 0; i < 10_000; i++)
report += $"Line {i}\n";
// Bom: monta tudo no mesmo buffer
var builder = new StringBuilder();
for (var i = 0; i < 10_000; i++)
builder.AppendLine($"Line {i}");
var finalReport = builder.ToString();
Para juntar os itens de uma coleção, existe ainda uma terceira opção. O string.Join concatena todos os elementos com um separador em uma única operação.
var names = new[] { "Ana", "Bruno", "Carla" };
var csv = string.Join(";", names); // Ana;Bruno;Carla
Performance de string e StringBuilder na prática
Para entender a diferença de performance entre string e StringBuilder, pense no trabalho que cada um faz dentro de um laço.
Com +=, cada volta cria uma nova string e copia todo o conteúdo acumulado até ali. Na primeira volta, o .NET copia poucos caracteres, mas na milésima ele copia o texto inteiro mais uma vez.
Em outras palavras, o total de caracteres copiados cresce de forma quadrática em relação ao número de repetições. Além disso, cada string descartada vira trabalho extra para o garbage collector.
Já o StringBuilder escreve cada novo pedaço no espaço livre do buffer. Quando o espaço acaba, ele reserva um novo bloco e continua, sem copiar o texto inteiro a cada Append.
Assim, o custo total cresce de forma aproximadamente linear. Por isso, a diferença fica pequena em poucas repetições e enorme em milhares delas.
Se quiser medir no seu próprio cenário, use uma biblioteca de benchmark, como o BenchmarkDotNet. Dessa forma, você compara tempo e memória alocada com dados reais, em vez de confiar em suposições.
Para cenários ainda mais exigentes, o artigo sobre StringBuilder.MoveChunks no .NET 11 mostra como a plataforma continua reduzindo alocações nessa classe.
Diferenças entre String e StringBuilder
Para resumir, a tabela abaixo compara String e StringBuilder lado a lado.
| Característica | String | StringBuilder |
|---|---|---|
| Namespace | System | System.Text |
| Mutabilidade | Imutável | Mutável |
| Concatenação | Cria uma nova string a cada operação | Altera o mesmo buffer |
| Uso ideal | Textos fixos e poucas junções | Muitas junções ou laços |
| Segurança entre threads | Segura, por ser imutável | Não é segura para escrita simultânea |
| Texto final | Já é o valor | Exige ToString() |
A imutabilidade da string traz vantagens. Por exemplo, você pode compartilhar a mesma string entre métodos e threads sem medo de alterações inesperadas.
Já o StringBuilder troca essa segurança por desempenho em cenários de montagem de texto. Por isso, cada tipo tem seu lugar no código.
Interpolação com string e StringBuilder
É importante não confundir a concatenação de strings com a interpolação de strings. Você pode usar a interpolação sempre que precisar, sinalizando para o compilador com o $ antes das aspas.
var myText = "World";
var otherText = $"Hello {myText}";
A interpolação deixa o código mais legível do que uma sequência de +. Além disso, a partir do C# 10 e do .NET 6, o compilador usa os interpolated string handlers, que reduzem alocações na montagem do texto.
O StringBuilder também aproveita esse recurso. Ao passar uma string interpolada para o Append, o .NET escreve as partes direto no buffer, sem criar uma string intermediária.
var builder = new StringBuilder();
var total = 150.75m;
// Escreve direto no buffer, sem string intermediária
builder.Append($"Total: {total:F2}");
Erros comuns com string e StringBuilder
Alguns erros aparecem com frequência quando o assunto é string e StringBuilder. Conhecê-los ajuda a evitar desperdício de memória e bugs difíceis de encontrar.
O primeiro é usar StringBuilder para juntar apenas duas ou três strings. Nesse caso, o custo de criar o objeto e chamar ToString supera o ganho.
O segundo é chamar ToString dentro do laço. Isso cria uma nova string a cada volta e anula o benefício do buffer.
O terceiro é esquecer que a string é imutável. Chamar text.Trim() sem atribuir o resultado não altera a variável text.
var text = " hello ";
text.Trim(); // Não altera text, o resultado se perde
text = text.Trim(); // Correto: guarda a nova string
Para ir além, o guia de manipulação de strings em C# e .NET reúne os principais métodos da classe String. Se o interesse for otimização avançada, o artigo sobre string interning no .NET mostra como o runtime reaproveita literais.
Curso de Fundamentos do C#
Quer aprender mais sobre C#? Então confira nosso curso Fundamentos do C#, que conta com mais de 100 aulas e 12.2 horas de conteúdo de forma simples, direta e objetiva.
Conclusão
Embora seja algo fundamental, a criação e o consumo de strings no C# têm suas pegadinhas. Conhecer a diferença entre string e StringBuilder pode economizar muito processamento e memória da sua aplicação.
Em resumo, String e string são o mesmo tipo, e a escolha entre eles é apenas estética. Já o StringBuilder é a ferramenta certa para montar textos grandes ou em laços.
No fim, usar bem string e StringBuilder resulta em menos consumo de recursos e redução de custo de infraestrutura. Ou seja, mais economia para o bolso da empresa!
FAQ
Qual a diferença entre String e string no C#?
Nenhuma no comportamento. A palavra-chave string é um alias para a classe System.String, e os dois geram o mesmo código.
A string é um tipo de valor ou de referência?
A string é um tipo de referência. No entanto, ela é imutável e compara conteúdo com ==, por isso parece um tipo de valor no uso diário.
Quando devo usar StringBuilder?
Use quando precisar juntar muitas partes de texto, principalmente dentro de laços. Para poucas concatenações, o + ou a interpolação bastam.
string.Empty e "" são iguais?
Sim, os dois representam uma string vazia e têm a mesma performance. A diferença é que "" é uma constante e string.Empty não.
O StringBuilder é seguro para várias threads?
Não. Se várias threads escreverem na mesma instância ao mesmo tempo, você precisa sincronizar o acesso.