C# Strings

Quase todo programa mexe com texto: o nome do cliente, a mensagem na tela, a senha, a lista de compras. Em C#, um texto é uma string (em inglês, "corrente", "fileira": uma fileira de letras). Você já usou strings desde o "Olá, mundo". Nesta aula você vai ver o que uma string é por dentro, como medir e pegar letras dela, como juntar textos, por que uma string nunca muda e como comparar textos do jeito certo, inclusive ignorando maiúsculas.

Você vai aprender
  • Criar strings, medir o tamanho com Length e pegar uma letra com nome[0].
  • Juntar textos com + e +=.
  • Por que a string é imutável e o que isso muda no seu código (nome = nome.ToUpper()).
  • Comparar: == compara o conteúdo; string.Equals com StringComparison ignora maiúsculas; string.Compare diz a ordem alfabética.
  • Maiúsculas e minúsculas em português ("ação".ToUpper()), string vazia × null e os erros mais comuns.

Todos os programas desta aula foram rodados com o .NET 10 na máquina fictícia da Ana (usuária ana, computador notebook, sistema em português do Brasil), na pasta ~/cs, com dotnet run arquivo.cs.


A ideia: um colar de letras

Imagine um colar de contas, cada conta com uma letra pintada. O colar "Ana" tem três contas: A, n e a. Dá para contar as contas, olhar a terceira, ou fazer um colar novo juntando dois colares. O que não dá é repintar uma conta: o colar vem lacrado de fábrica. Para mudar uma letra, você faz um colar novo.

Uma string é exatamente isso: uma sequência de caracteres (char, aula char e bool), numerados a partir do zero, que não pode ser alterada depois de criada.

Posição012345678
LetraAna(espaço)Souza
A string "Ana Souza": 9 caracteres, nas posições 0 a 8. O espaço também é um caractere.

1. Criar uma string e medir o tamanho

string nome = "Ana";
string cidade = "São Paulo";
string vazio = "";
Console.WriteLine(nome);
Console.WriteLine(nome.Length);
Console.WriteLine(cidade.Length);
Console.WriteLine(vazio.Length);
Console.WriteLine(" ".Length);
$ dotnet run criar.cs
Ana
3       « A, n, a
9       « S, ã, o, espaço, P, a, u, l, o: o ã conta como um, e o espaço também
0       « a string vazia não tem nenhum caractere
1       « um espaço é um caractere
string nome = "Ana";
Cria uma variável do tipo string com o texto Ana. O texto vai entre aspas duplas; as aspas não fazem parte dele. (Aspas simples, 'A', são para um char, uma letra só.)
""
A string vazia: um texto sem nenhum caractere. Existe, só não tem nada dentro, como um colar sem contas.
nome.Length
"Comprimento": quantos caracteres a string tem. É uma propriedade, então vem sem parênteses (Length, não Length()) e com L maiúsculo.

string com letra minúscula é o apelido que o C# dá ao tipo System.String do .NET. String nome = "Ana"; também funciona e é exatamente o mesmo tipo; o costume é escrever string, minúsculo, como int e bool.


2. Cada letra tem uma posição

Para pegar um caractere, escreva a posição entre colchetes depois da variável. A contagem começa em zero:

string nome = "Ana Souza";
Console.WriteLine(nome[0]);
Console.WriteLine(nome[4]);
Console.WriteLine(nome[nome.Length - 1]);
Console.WriteLine(nome[^1]);
char primeira = nome[0];
Console.WriteLine(primeira);
$ dotnet run indice.cs
A       « a posição 0 é a primeira letra
S       « posição 4 (contando o espaço na posição 3)
a       « a última: posição Length - 1 = 8
a       « ^1 = "a primeira a partir do fim"
A
nome[0]
O caractere na posição 0, o primeiro. O resultado é um char, não uma string: por isso dá para guardar em char primeira.
nome[nome.Length - 1]
A última posição é sempre o tamanho menos 1: "Ana Souza" tem 9 caracteres, nas posições 0 a 8. Pedir nome[9] derruba o programa (veja Quando dá errado).
nome[^1] C# 8
O ^ conta de trás para a frente: ^1 é o último, ^2 o penúltimo. É o mesmo que nome[nome.Length - 1], mais curto (aula Índices e Intervalos).

Experimente: troque o nome pelo seu e veja o tamanho e as letras mudarem. Tente acentos ("José"), espaços no fim ("Ana ") e a string vazia "". O que acontece com nome[0] quando o nome é vazio?

string nome = "Beatriz";
Console.WriteLine("Nome: " + nome);
Console.WriteLine("Tamanho: " + nome.Length);
Console.WriteLine("Primeira letra: " + nome[0]);
Console.WriteLine("Última letra: " + nome[^1]);

3. Juntar textos: + e +=

Juntar strings se chama concatenar ("encadear"). O + entre dois textos cola um no fim do outro:

string nome = "Ana";
string sobrenome = "Souza";
string completo = nome + " " + sobrenome;
Console.WriteLine(completo);
int idade = 29;
Console.WriteLine(nome + " tem " + idade + " anos");
string lista = "arroz";
lista += ", feijão";
lista += ", café";
Console.WriteLine(lista);
$ dotnet run juntar.cs
Ana Souza
Ana tem 29 anos
arroz, feijão, café
nome + " " + sobrenome
Três pedaços colados: "Ana", um espaço e "Souza". O + não põe espaço sozinho; se você esquecer o " ", sai AnaSouza.
" tem " + idade
Quando um lado é texto e o outro é número, o número vira texto e é colado. Cuidado com a ordem: "Total: " + 2 + 3 dá Total: 23 (aula Precedência).
lista += ", feijão";
"Cole isto no fim de lista": é o mesmo que lista = lista + ", feijão"; (aula Operadores de Atribuição).

Com muitos pedaços, o + fica difícil de ler. A interpolação, $"{nome} tem {idade} anos", é o jeito moderno, e tem uma aula só para ela: Interpolação.


4. Strings são imutáveis

Lembra do colar lacrado? Uma string, depois de criada, nunca muda. Os métodos que "mudam" um texto, como ToUpper() ("para maiúsculas"), na verdade criam uma string nova e devolvem essa nova. A original fica igual:

string nome = "ana";
nome.ToUpper();
Console.WriteLine(nome);
string maiusculo = nome.ToUpper();
Console.WriteLine(maiusculo);
Console.WriteLine(nome);
nome = nome.ToUpper();
Console.WriteLine(nome);
$ dotnet run imutavel.cs
ana      « a linha 2 criou "ANA" e jogou fora: ninguém guardou
ANA      « agora a string nova foi guardada em maiusculo
ana      « a original continua igual
ANA      « nome passou a apontar para a string nova
  1. Linha 2: nome.ToUpper() criou a string "ANA", mas o resultado não foi guardado em lugar nenhum. O compilador não avisa, e a linha não serve para nada.
  2. Linha 4: o resultado foi guardado numa variável nova. nome continua "ana".
  3. Linha 7: nome = nome.ToUpper(); guarda a string nova na própria variável. A string antiga, "ana", não foi alterada: a variável é que passou a guardar outra string. É assim que se "muda" um texto em C#.

Isso também explica por que mudar uma variável não afeta a outra:

string a = "Oi";
string b = a;
a += ", tudo bem?";
Console.WriteLine(a);
Console.WriteLine(b);
$ dotnet run copia.cs
Oi, tudo bem?
Oi       « b continua com a string antiga

O a += … criou uma string nova e guardou em a. A string "Oi", que b guarda, nunca foi tocada. E tentar trocar uma letra diretamente, com nome[0] = 'A', é recusado pelo compilador (erro CS0200).

Por que imutável?

Porque é seguro: uma string que você passou para outra parte do programa nunca vai mudar "por baixo" de você. O preço é que cada += cria uma string nova. Para juntar milhares de pedaços num laço, existe o StringBuilder, que é mutável (aula StringBuilder).

Missão: o crachá do evento deve mostrar o nome em maiúsculas, mas sai Crachá: ana souza. A linha do ToUpper está lá, só que o resultado se perde. Conserte para aparecer Crachá: ANA SOUZA, sem mudar o texto "ana souza".

Crachá: ANA SOUZA
string nome = "ana souza";
nome.ToUpper();
Console.WriteLine("Crachá: " + nome);
string nome = "ana souza";
nome = nome.ToUpper();
Console.WriteLine("Crachá: " + nome);

5. Comparar textos

== compara o conteúdo

Em C#, == com strings compara o texto, letra por letra. Não importa como cada string foi criada:

string senha = "gato123";
string digitada = "gato" + "123";
Console.WriteLine(senha == digitada);
Console.WriteLine(senha == "Gato123");
Console.WriteLine(senha.Equals(digitada));
Console.WriteLine(string.Equals(senha, "GATO123", StringComparison.OrdinalIgnoreCase));
Console.WriteLine(string.Equals(senha, "GATO123", StringComparison.CurrentCultureIgnoreCase));
Console.WriteLine("Recife".Equals("RECIFE", StringComparison.OrdinalIgnoreCase));
$ dotnet run igual.cs
True     « mesmo texto, mesmo que montado com +
False    « G maiúsculo é outra letra
True     « Equals sem opções faz o mesmo que ==
True     « ignorando maiúsculas e minúsculas
True
True
senha == digitada
Compara o conteúdo. Quem vem do Java estranha: lá, == com texto é uma armadilha. Em C#, é o jeito normal.
senha == "Gato123"
Maiúsculas e minúsculas contam: g e G são caracteres diferentes.
string.Equals(a, b, StringComparison.OrdinalIgnoreCase)
Compara a e b ignorando maiúsculas. O terceiro valor, do tipo StringComparison ("comparação de strings"), diz como comparar. Também pode ser escrito como a.Equals(b, StringComparison.OrdinalIgnoreCase).

As opções de StringComparison

OpçãoComo comparaUse para
OrdinalCódigo por código (é o que o == faz)senhas, códigos, nomes de arquivo, comandos
OrdinalIgnoreCaseCódigo por código, ignorando maiúsculasrespostas do usuário ("sim", "SIM"), e-mails, comandos
CurrentCulturePelas regras do idioma do computador (aqui, pt-BR)ordenar e comparar textos que pessoas vão ler
CurrentCultureIgnoreCaseIdem, ignorando maiúsculasbusca por nome numa lista mostrada ao usuário
InvariantCulture, InvariantCultureIgnoreCaseRegras fixas, iguais em qualquer computadordados gravados em arquivo que precisam dar o mesmo resultado em qualquer lugar

Qual usar? Na dúvida, Ordinal ou OrdinalIgnoreCase: são rápidos e dão sempre o mesmo resultado, em qualquer computador. Use as opções de cultura quando a regra do idioma importa, como na ordem alfabética de uma lista de nomes. A aula Cultura mostra as diferenças em detalhe.

Ordem alfabética: string.Compare

Como você viu em Operadores de Comparação, < e > não funcionam com strings. Para saber quem vem antes, use string.Compare(a, b). Ele devolve um número: negativo se a vem antes, zero se são iguais, positivo se a vem depois.

Console.WriteLine(string.Compare("ana", "Bia"));
Console.WriteLine(string.Compare("Bia", "ana"));
Console.WriteLine(string.Compare("ana", "ana"));
Console.WriteLine(string.Compare("ana", "Bia", StringComparison.Ordinal));
Console.WriteLine(string.Compare("é", "f"));
Console.WriteLine(string.Compare("é", "f", StringComparison.Ordinal));
$ dotnet run ordem.cs
-1      « "ana" vem antes de "Bia" no dicionário
1       « "Bia" vem depois de "ana"
0       « iguais
31      « Ordinal: 'a' (97) vem DEPOIS de 'B' (66) na tabela de códigos
-1      « no dicionário, "é" vem antes de "f"
131     « Ordinal: 'é' (233) vem muito depois de 'f' (102)

Sem a opção, string.Compare usa as regras do português: maiúsculas e acentos ficam no lugar que você espera num dicionário. Com Ordinal, vale o número de cada caractere, e aí todas as maiúsculas vêm antes das minúsculas e as letras acentuadas vão para o fim. Para ordenar nomes que a pessoa vai ver, não use Ordinal. Não conte com o valor exato (31, 131): só o sinal (negativo, zero, positivo) importa.


6. Maiúsculas e minúsculas em português

ToUpper() ("para cima") e ToLower() ("para baixo") conhecem os acentos:

string palavra = "ação";
Console.WriteLine(palavra.ToUpper());
Console.WriteLine("SÃO JOÃO".ToLower());
Console.WriteLine("istanbul".ToUpper());
Console.WriteLine("istanbul".ToUpper(new System.Globalization.CultureInfo("tr-TR")));
Console.WriteLine("istanbul".ToUpperInvariant());
Console.WriteLine(System.Globalization.CultureInfo.CurrentCulture.Name);
$ dotnet run upper.cs
AÇÃO        « ç vira Ç, ã vira Ã
são joão
ISTANBUL    « pelas regras do português
İSTANBUL    « pelas regras do turco: o i minúsculo vira İ, com ponto!
ISTANBUL    « Invariant: regras fixas, iguais em qualquer computador
pt-BR       « a cultura da máquina da Ana
ToUpper() e ToLower()
Sem nada entre os parênteses, usam a cultura atual do computador (aqui, pt-BR). Para textos em português, o resultado é o que você espera.
O caso do turco
Em turco, existem dois "i": com ponto (i, İ) e sem ponto (ı, I). Num computador em turco, "istanbul".ToUpper() dá İSTANBUL, e uma comparação como comando.ToUpper() == "ISTANBUL" falha. Por isso, para comparar códigos e comandos, prefira string.Equals(…, StringComparison.OrdinalIgnoreCase) em vez de converter para maiúsculas.
ToUpperInvariant()
Usa regras fixas, que não dependem do idioma do computador. Bom para códigos ("br".ToUpperInvariant() → BR).

7. String vazia × null

Uma string vazia ("") é um colar sem contas: ele existe. null é não ter colar nenhum: a variável não aponta para string alguma (aula null). Para guardar null, o tipo leva um ?: string? ("string ou nada", aula Referências Anuláveis).

string? apelido = null;
string vazio = "";
Console.WriteLine(string.IsNullOrEmpty(apelido));
Console.WriteLine(string.IsNullOrEmpty(vazio));
Console.WriteLine(string.IsNullOrWhiteSpace("   "));
Console.WriteLine(apelido == null);
Console.WriteLine(vazio == string.Empty);
Console.WriteLine(apelido.Length);
$ dotnet run nulo.cs
/home/ana/cs/nulo.cs(8,19): warning CS8602: Desreferência de uma referência possivelmente nula.
True
True
True     « só espaços: "em branco"
True
True     « string.Empty é o mesmo que ""
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Program.<Main>$(String[] args) in /home/ana/cs/nulo.cs:line 8
string.IsNullOrEmpty(texto)
"É nulo ou vazio?" Responde true para null e para "". É o teste mais usado antes de mexer num texto que veio de fora.
string.IsNullOrWhiteSpace(texto)
"É nulo, vazio ou só espaços?" Ideal para conferir se o usuário digitou alguma coisa (aula Ler do Teclado).
apelido.Length
Pedir o tamanho de "colar nenhum" não faz sentido: o programa termina com NullReferenceException ("referência de objeto não definida para uma instância de um objeto"), código de saída 134. O compilador avisou antes, com o CS8602: "desreferência de uma referência possivelmente nula", isto é, "você está usando algo que pode ser null".

Formas diferentes de fazer a mesma coisa

FormasQual usar
string × Stringstring. São o mesmo tipo; o minúsculo é o costume.
"" × string.EmptyTanto faz: são iguais. Muitos times preferem string.Empty porque deixa claro que o vazio é de propósito.
a == b × a.Equals(b)==. E a.Equals(b) quebra se a for null; == não.
a.ToUpper() == b.ToUpper() × string.Equals(a, b, StringComparison.OrdinalIgnoreCase)O string.Equals: não cria strings novas, não depende do idioma e aceita null.
nome[nome.Length - 1] × nome[^1]nome[^1] C# 8, mais curto. Em código antigo você vai ver a outra.
a + " " + b × $"{a} {b}"A interpolação, quando há mais de dois pedaços (aula Interpolação).

Quando dá errado

error CS0200: A propriedade ou o indexador "string.this[int]" não pode ser atribuído, pois é somente leitura
string nome = "ana";
nome[0] = 'A';
Console.WriteLine(nome);
$ dotnet run es1.cs
/home/ana/cs/es1.cs(2,1): error CS0200: A propriedade ou o indexador "string.this[int]" não pode ser atribuído, pois é somente leitura

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: as posições de uma string são "somente leitura": dá para olhar nome[0], mas não trocar. É a imutabilidade.

Como consertar: crie uma string nova: nome = char.ToUpper(nome[0]) + nome[1..]; (o [1..], "da posição 1 até o fim", aparece em Métodos de String).

Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
string nome = "Ana";
Console.WriteLine(nome[3]);
$ dotnet run es2.cs
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.String.get_Chars(Int32 index)
   at Program.<Main>$(String[] args) in /home/ana/cs/es2.cs:line 2

O que significa: "o índice estava fora dos limites". "Ana" tem as posições 0, 1 e 2; a 3 não existe. A linha at System.String.get_Chars mostra onde o .NET percebeu (dentro da string); a de baixo mostra a sua linha, a 2. O código de saída é 134.

Como consertar: a última posição é nome.Length - 1 (ou nome[^1]). Se o texto pode ser vazio, confira antes: nome.Length > 0.

error CS1012: Número excessivo de caracteres no literal de caractere
char letra = 'Ana';
$ dotnet run es3.cs
/home/ana/cs/es3.cs(1,14): error CS1012: Número excessivo de caracteres no literal de caractere

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: aspas simples são para um caractere só (char). Para um texto, aspas duplas.

Como consertar: string nome = "Ana"; (ou char letra = 'A';).

error CS0019: O operador "==" não pode ser aplicado a operandos dos tipos "string" e "char"
string inicial = "A";
Console.WriteLine(inicial == 'A');
$ dotnet run es4.cs
/home/ana/cs/es4.cs(2,19): error CS0019: O operador "==" não pode ser aplicado a operandos dos tipos "string" e "char"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: "A" (uma string de um caractere) e 'A' (um char) são tipos diferentes.

Como consertar: compare string com string (inicial == "A") ou char com char (inicial[0] == 'A').

error CS1061: ‘string’ não contém uma definição para "length"…
string nome = "Ana";
Console.WriteLine(nome.length);
$ dotnet run es5.cs
/home/ana/cs/es5.cs(2,24): error CS1061: ‘string’ não contém uma definição para "length" e não foi possível encontrar nenhum método de extensão "length" que aceite um primeiro argumento do tipo ‘string’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?)

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: o C# diferencia maiúsculas de minúsculas também nos nomes: length não existe, Length existe. A mensagem longa só quer dizer "não achei nada com esse nome".

Como consertar: nome.Length. Os nomes do .NET começam com maiúscula: Length, ToUpper, WriteLine.

NullReferenceException ao usar um texto

O que significa: a variável era null e você pediu .Length, .ToUpper() ou outro membro. O aviso CS8602 costuma aparecer antes, ao compilar.

Como consertar: confira antes com string.IsNullOrEmpty(texto), ou dê um valor padrão (texto ?? "", aula Operadores ??, ?. e ??=).

O texto não mudou depois do ToUpper() (ou Trim(), Replace()…)

O que significa: você chamou o método e não guardou o resultado. Strings são imutáveis: o método devolve uma string nova.

Como consertar: nome = nome.ToUpper();.


Referência

CódigoO que fazExemplo → resultado
"texto"cria uma stringstring s = "Ana";
"" ou string.Emptystring vazia"".Length → 0
s.Lengthquantidade de caracteres"Ana".Length → 3
s[i]caractere na posição i (um char)"Ana"[0] → 'A'
s[^1]último caractere"Ana"[^1] → 'a'
a + b, s += xjunta textos (cria uma string nova)"Oi" + "!" → "Oi!"
a == b, a != bcompara o conteúdo"Ana" == "ana" → false
string.Equals(a, b, StringComparison.OrdinalIgnoreCase)compara ignorando maiúsculas"Ana" e "ANA" → true
string.Compare(a, b)ordem alfabética: < 0, 0 ou > 0("ana", "Bia") → negativo
s.ToUpper(), s.ToLower()nova string em maiúsculas / minúsculas (cultura atual)"ação".ToUpper() → "AÇÃO"
s.ToUpperInvariant()maiúsculas com regras fixas"br" → "BR"
string.IsNullOrEmpty(s)null ou ""?("") → true
string.IsNullOrWhiteSpace(s)null, "" ou só espaços?(" ") → true

Muitos outros métodos (Trim, Contains, Replace, Split, Substring…) estão na próxima aula, Métodos de String.


Exercícios

1. Tamanho

Complete para mostrar quantos caracteres tem cidade:

Console.WriteLine(cidade.);

2. A primeira letra

Complete para pegar o primeiro caractere de nome:

char inicial = nome[];

3. Guardar o resultado

Complete para que nome fique em minúsculas:

nome nome.ToLower();

4. Quanto vale "Rio de Janeiro".Length?

5. Depois de string s = "oi"; s.ToUpper(); Console.WriteLine(s);, o que aparece?

6. Qual comparação dá true para "sim" e "SIM"?

7. string.Compare("Bia", "ana") dá um número positivo. O que isso quer dizer?

Missão final: o jogo de perguntas

Missão: num jogo de perguntas, a cidade certa é Recife e a jogadora respondeu RECIFE. Conserte a comparação para ignorar maiúsculas (use string.Equals com StringComparison), e troque o 0 da última linha pelo tamanho da palavra, calculado com Length (não escreva o 6 à mão).

Acertou? True
A palavra tem 6 letras
string cidade = "Recife";
string resposta = "RECIFE";
bool acertou = resposta == cidade;
Console.WriteLine("Acertou? " + acertou);
Console.WriteLine("A palavra tem " + 0 + " letras");
string cidade = "Recife";
string resposta = "RECIFE";
bool acertou = string.Equals(resposta, cidade, StringComparison.OrdinalIgnoreCase);
Console.WriteLine("Acertou? " + acertou);
Console.WriteLine("A palavra tem " + cidade.Length + " letras");

Resumo

  • Uma string é uma sequência de char, entre aspas duplas. Length (sem parênteses) dá o tamanho.
  • As posições começam em 0: s[0] é o primeiro, s[s.Length - 1] ou s[^1] o último. Fora disso, IndexOutOfRangeException.
  • + e += juntam textos, criando strings novas.
  • Strings são imutáveis: métodos devolvem uma string nova. Guarde o resultado: nome = nome.ToUpper();. s[0] = 'A' dá CS0200.
  • == compara o conteúdo. Para ignorar maiúsculas: string.Equals(a, b, StringComparison.OrdinalIgnoreCase).
  • string.Compare(a, b) diz a ordem: negativo, zero ou positivo. Sem opção, segue o português; com Ordinal, os códigos.
  • ToUpper() segue a cultura ("ação" → "AÇÃO"); para códigos, ToUpperInvariant().
  • "" é vazio; null é nada. Teste com string.IsNullOrEmpty ou IsNullOrWhiteSpace.