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.

CURSO FUNDAMENTOS DO C#

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.