C# Cultura

Desde as primeiras aulas, o Console.WriteLine(3.5) mostra 3,5, com vírgula, e o dinheiro aparece como R$ 1.234,50. Nos Estados Unidos, o mesmo programa mostra 3.5 e $1,234.50. Quem decide é a cultura: o conjunto de costumes de um país para escrever números, datas e moedas. Nesta aula você vai ver de onde ela vem, como escrever números do jeito que outros programas entendem, e por que double.Parse("3.5") dá 35 num computador brasileiro. Outras aulas do guia trazem você para cá quando aparece esse 3,5.

Você vai aprender
  • O que é a cultura, de onde ela vem (CultureInfo.CurrentCulture e a variável LANG) e o que ela muda.
  • Como o mesmo número e a mesma data aparecem em vários países.
  • Escrever números para máquinas com ToString(CultureInfo.InvariantCulture).
  • Ler números com ponto: double.Parse("3.5", CultureInfo.InvariantCulture) e o TryParse com cultura.
  • Usar a cultura de um país específico: new CultureInfo("en-US").
  • Mudar a cultura do programa inteiro, e como a cultura muda a ordem de textos com acentos.

Esta aula usa Formatar Números, Parse e TryParse e Datas e Horas. Os programas rodam com dotnet run na pasta ~/cs do computador da Ana, que está em português do Brasil.


A ideia: cada país escreve de um jeito

No Brasil, "mil duzentos e trinta e quatro reais e cinquenta centavos" se escreve R$ 1.234,50: ponto separa os milhares e vírgula separa os centavos. Nos Estados Unidos é o contrário: $1,234.50. E a data de hoje é 17/10/2026 aqui e 10/17/2026 lá, com o mês primeiro.

O número guardado na memória é o mesmo; muda só o jeito de escrever e de ler o texto. A cultura funciona como um tradutor de costumes: cada vez que um número ou uma data vira texto (no WriteLine, no ToString, na interpolação) ou um texto vira número (no Parse), o .NET pergunta ao tradutor como fazer.

O que a cultura decidept-BRen-US
Separador decimal3,53.5
Separador de milhar1.2341,234
Moeda ("C")R$ 1.234,50$1,234.50
Data curta ("d")17/10/202610/17/2026
Nomes de dias e mesessábado, outubroSaturday, October
Ordem alfabética e maiúsculasregras de cada língua (seção 6)

1. A cultura atual: CultureInfo.CurrentCulture

A classe CultureInfo ("informação de cultura") fica no namespace System.Globalization (de "globalização"), que precisa de using:

using System.Globalization;

CultureInfo atual = CultureInfo.CurrentCulture;
Console.WriteLine(atual.Name);
Console.WriteLine(atual.DisplayName);
Console.WriteLine($"Decimal: '{atual.NumberFormat.NumberDecimalSeparator}'");
Console.WriteLine($"Milhar: '{atual.NumberFormat.NumberGroupSeparator}'");
Console.WriteLine($"Moeda: '{atual.NumberFormat.CurrencySymbol}'");
$ dotnet run atual.cs
pt-BR
português (Brasil)
Decimal: ','
Milhar: '.'
Moeda: 'R$'
$ LANG=en_US.UTF-8 dotnet run atual.cs        « o mesmo programa, "como se" o computador fosse americano
en-US
English (United States)
Decimal: '.'
Milhar: ','
Moeda: '$'
CultureInfo.CurrentCulture
"A cultura atual": a que o programa usa quando você não diz nenhuma. Ela vem do sistema operacional. No Linux, da variável de ambiente LANG, que no Ubuntu da Ana vale pt_BR.UTF-8; no Windows e no macOS, das configurações de região.
atual.Name
O nome da cultura: a língua (pt, português) e o país (BR, Brasil). pt-PT é o português de Portugal, en-US o inglês dos Estados Unidos, en-GB o do Reino Unido.
atual.DisplayName
O nome para mostrar, na língua da própria cultura.
atual.NumberFormat
As regras de números da cultura: NumberDecimalSeparator (separador decimal), NumberGroupSeparator (de milhar), CurrencySymbol (símbolo da moeda).
LANG=en_US.UTF-8 dotnet run atual.cs
No terminal do Linux, VARIAVEL=valor comando roda o comando com a variável trocada, só daquela vez (o Guia Bash explica). Foi o suficiente para o programa virar americano.

É por isso que um programa pode funcionar no seu computador e dar resultado diferente no de outra pessoa, ou num servidor configurado em inglês: o código é o mesmo, a cultura não.


2. O mesmo valor em vários países

O ToString de números e datas aceita, depois do formato, a cultura que você quer usar:

using System.Globalization;

decimal preco = 1234.5m;
DateTime data = new DateTime(2026, 10, 17);
string[] nomes = ["pt-BR", "pt-PT", "en-US", "en-GB", "es-ES", "de-DE", "ja-JP"];
foreach (string nome in nomes)
{
    CultureInfo c = new CultureInfo(nome);
    Console.WriteLine($"{nome}: {preco.ToString("N2", c)} | {preco.ToString("C", c)} | {data.ToString("d", c)} | {data.ToString("D", c)}");
}
CultureInfo inv = CultureInfo.InvariantCulture;
Console.WriteLine($"Invariante: {preco.ToString("N2", inv)} | {preco.ToString("C", inv)} | {data.ToString("d", inv)}");
$ dotnet run paises.cs
pt-BR: 1.234,50 | R$ 1.234,50 | 17/10/2026 | sábado, 17 de outubro de 2026
pt-PT: 1 234,50 | 1 234,50 € | 17/10/2026 | sábado, 17 de outubro de 2026
en-US: 1,234.50 | $1,234.50 | 10/17/2026 | Saturday, October 17, 2026
en-GB: 1,234.50 | £1,234.50 | 17/10/2026 | Saturday, 17 October 2026
es-ES: 1.234,50 | 1.234,50 € | 17/10/2026 | sábado, 17 de octubre de 2026
de-DE: 1.234,50 | 1.234,50 € | 17.10.2026 | Samstag, 17. Oktober 2026
ja-JP: 1,234.50 | ¥1,235 | 2026/10/17 | 2026年10月17日土曜日
Invariante: 1,234.50 | ¤1,234.50 | 10/17/2026
new CultureInfo("en-US")
Cria a cultura pelo nome. (CultureInfo.GetCultureInfo("en-US") faz o mesmo e reaproveita o objeto, bom para usar muitas vezes.)
preco.ToString("C", c)
O formato "C" (moeda) com as regras da cultura c: símbolo, posição do símbolo e separadores. Repare no Japão: o iene não tem centavos, e 1234,5 foi arredondado para 1,235.
pt-PT: 1 234,50
Em Portugal, o milhar é separado por um espaço especial, que não quebra a linha (não é o espaço comum da barra de espaço).
CultureInfo.InvariantCulture
A cultura invariante, "que não varia": não é de país nenhum. Usa ponto nos decimais, vírgula no milhar, datas mês/dia/ano e o símbolo genérico de moeda ¤. Ela é igual em todo computador, em qualquer configuração. Para que serve? Seção 3.
No editor do guia

As linhas de pt-BR, pt-PT, en-US, en-GB, es-ES e de-DE e a invariante saem iguais às do terminal. A do Japão, não: o editor não traz os dados de japonês, e a linha sai ja-JP: 1.234,50 | ¥ 1.235 | 17/10/2026 | sábado, 17 de outubro de 2026 (só o iene muda; o resto fica no formato do Brasil). Para ver as culturas da Ásia, rode no terminal.

Atenção: "C" só troca o desenho da moeda, não converte o valor. 1234,5 reais escritos com $ continuam sendo 1234,5, e não viram dólares.


3. Escrever para máquinas: InvariantCulture

Para pessoas, a cultura atual é ótima: cada uma vê os números do jeito do seu país. Mas quando o texto vai para outro programa (um arquivo CSV, uma planilha que será importada, um site, um banco de dados), o formato tem de ser sempre o mesmo, em qualquer computador. Para isso existe a cultura invariante:

using System.Globalization;

double nota = 3.5;
Console.WriteLine(nota);
Console.WriteLine(nota.ToString());
Console.WriteLine(nota.ToString(CultureInfo.InvariantCulture));
Console.WriteLine($"Nota: {nota}");
Console.WriteLine(string.Create(CultureInfo.InvariantCulture, $"Nota: {nota}"));
Console.WriteLine(FormattableString.Invariant($"Nota: {nota}"));
$ dotnet run invariante.cs
3,5
3,5
3.5
Nota: 3,5
Nota: 3.5
Nota: 3.5
Console.WriteLine(nota) e nota.ToString()
Os dois usam a cultura atual: 3,5.
nota.ToString(CultureInfo.InvariantCulture)
Com a cultura invariante: 3.5, com ponto, em qualquer computador. Também dá para juntar um formato: nota.ToString("F2", CultureInfo.InvariantCulture) dá 3.50.
$"Nota: {nota}"
A interpolação também usa a cultura atual.
string.Create(CultureInfo.InvariantCulture, $"…")
Monta a string interpolada inteira com a cultura que você escolher .NET 6. Todos os números de dentro saem com ponto.
FormattableString.Invariant($"…")
Um atalho mais antigo para o mesmo, só com a cultura invariante.

Missão: a Ana quer gerar um CSV, com os campos separados por vírgula, para a escola importar. Mas a vírgula da nota se mistura com a do separador: Ana,8,5 parece ter três colunas. Escreva a nota com notas[i].ToString(CultureInfo.InvariantCulture) para as linhas ficarem Ana,8.5, Bruno,7 e Carla,9.25.

using System.Globalization;

string[] alunos = ["Ana", "Bruno", "Carla"];
double[] notas = [8.5, 7, 9.25];
for (int i = 0; i < alunos.Length; i++)
{
    Console.WriteLine($"{alunos[i]},{notas[i]}");
}
using System.Globalization;

string[] alunos = ["Ana", "Bruno", "Carla"];
double[] notas = [8.5, 7, 9.25];
for (int i = 0; i < alunos.Length; i++)
{
    Console.WriteLine($"{alunos[i]},{notas[i].ToString(CultureInfo.InvariantCulture)}");
}

Outra saída, muito usada nas planilhas brasileiras, é trocar o separador do CSV por ;, como nas aulas Arquivos e Ler e Escrever. Mesmo assim, um arquivo que vai para outro programa fica mais seguro com a cultura invariante, porque assim ele é igual em qualquer computador.


4. Ler números: Parse com cultura

O caminho de volta tem a mesma regra: o Parse lê o texto com os costumes da cultura atual, se você não disser outra. E é aqui que mora o erro mais traiçoeiro desta aula:

using System.Globalization;

Console.WriteLine(double.Parse("3,5"));
Console.WriteLine(double.Parse("3.5"));
Console.WriteLine(double.Parse("1.234,5"));
Console.WriteLine(double.Parse("3.5", CultureInfo.InvariantCulture));

if (double.TryParse("3.5", NumberStyles.Float, CultureInfo.InvariantCulture, out double x))
{
    Console.WriteLine($"Li {x}");
}
$ dotnet run ler.cs
3,5
35
1234,5
3,5
Li 3,5
double.Parse("3,5")
Em pt-BR, a vírgula é o separador decimal: três e meio.
double.Parse("3.5") → 35
Em pt-BR, o ponto é o separador de milhar, e o Parse aceita e ignora separadores de milhar (como em 1.234). Sobrou 35. Nenhum erro, nenhum aviso: só um número dez vezes maior. Uma nota 8.5 vira 85, um preço de 2.50 vira 250.
double.Parse("1.234,5")
O formato brasileiro completo, com milhar e decimal: 1234,5.
double.Parse("3.5", CultureInfo.InvariantCulture)
Diz ao Parse que o texto está no formato invariante, com ponto: três e meio, mostrado depois como 3,5 pelo WriteLine.
double.TryParse("3.5", NumberStyles.Float, CultureInfo.InvariantCulture, out double x)
A versão segura, sem exceção. Com cultura, o TryParse pede também o estilo (NumberStyles): Float aceita sinal, decimais e expoente (1e3); Number aceita também o separador de milhar. Para decimal, use NumberStyles.Number.
O texto vem de…Leia com
Uma pessoa, digitando no seu programaA cultura atual: double.TryParse(texto, out double x)
Um arquivo, um site ou outro programa, com pontoCultureInfo.InvariantCulture
Um sistema de um país conhecidoA cultura dele: new CultureInfo("en-US")
JSONO System.Text.Json já usa ponto sempre (aula JSON)

5. A cultura de um país: new CultureInfo

A lojinha da Ana comprou de um fornecedor dos Estados Unidos, e o pedido veio no formato de lá, com vírgula no milhar e ponto nos centavos:

$ cat pedido.txt
Notebook;1,299.90
Mouse;49.90
using System.Globalization;

CultureInfo eua = new CultureInfo("en-US");
decimal total = 0;
foreach (string linha in File.ReadAllLines("pedido.txt"))
{
    string[] campos = linha.Split(';');
    decimal preco = decimal.Parse(campos[1], eua);
    Console.WriteLine($"{campos[0]}: {preco:C}");
    total += preco;
}
Console.WriteLine($"Total: {total:C}");
$ dotnet run fornecedor.cs
Notebook: R$ 1.299,90
Mouse: R$ 49,90
Total: R$ 1.349,80
decimal.Parse(campos[1], eua)
Lê o texto com os costumes americanos: em 1,299.90, a vírgula é o milhar e o ponto é o decimal.
{preco:C}
Mostra com a cultura atual, a brasileira: R$ 1.299,90. Ler com uma cultura e mostrar com outra é normal: o número no meio não tem cultura nenhuma.

6. Mudar a cultura do programa, e os textos

Você pode trocar a cultura atual de dentro do programa. Tudo o que vier depois usa a nova:

using System.Globalization;

Console.WriteLine(1234.5m.ToString("C"));
CultureInfo.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine(3.5);
Console.WriteLine(1234.5m.ToString("C"));
$ dotnet run trocar.cs
R$ 1.234,50
3.5
$1,234.50

Faça isso só quando o programa inteiro deve seguir um país (por exemplo, um relatório que sempre sai em formato brasileiro, até no servidor em inglês). Para um ponto só do programa, passe a cultura no ToString e no Parse, como nas seções anteriores: fica claro para quem lê o código.

Ordenar e comparar textos

A cultura também conhece as regras de ordem alfabética da língua, inclusive os acentos. Já a comparação ordinal ("pela ordem dos números") compara os códigos das letras, sem conhecer língua nenhuma:

string[] nomes = ["Zé", "ana", "Álvaro", "Bruno", "Érica", "carla"];
Console.WriteLine(string.Join(" ", nomes.Order()));
Console.WriteLine(string.Join(" ", nomes.Order(StringComparer.Ordinal)));
Console.WriteLine(string.Equals("SÃO PAULO", "são paulo", StringComparison.CurrentCultureIgnoreCase));
Console.WriteLine(string.Equals("foto.JPG", "foto.jpg", StringComparison.OrdinalIgnoreCase));
$ dotnet run ordem.cs
Álvaro ana Bruno carla Érica Zé
Bruno Zé ana carla Álvaro Érica
True
True
nomes.Order()
Ordena com a cultura atual: a ordem de um dicionário em português, com Álvaro junto dos A e sem separar maiúsculas de minúsculas.
Order(StringComparer.Ordinal)
Pelos códigos das letras: todas as maiúsculas sem acento vêm antes das minúsculas, e as letras acentuadas vão para o fim. Para mostrar a pessoas, parece errado.
StringComparison.CurrentCultureIgnoreCase
Compara com as regras da língua, sem diferenciar maiúsculas: SÃO PAULO é igual a são paulo.
StringComparison.OrdinalIgnoreCase
Compara os códigos, sem diferenciar maiúsculas. É o certo para coisas de computador: nomes de arquivo, extensões, comandos, chaves. É mais rápido e não muda de país para país.
Um servidor sem culturas

Alguns servidores e contêineres (como imagens Docker bem pequenas) rodam o .NET no modo de globalização invariante, sem os dados das culturas. Nele, a cultura atual é sempre a invariante, e o programa mostra 3.5 mesmo com LANG=pt_BR.UTF-8. Dá para ver esse modo com a variável DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1:

$ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 dotnet run invariante.cs
3.5
3.5
3.5
Nota: 3.5
Nota: 3.5
Nota: 3.5

Mais um motivo para passar a cultura sempre que o formato importa.


Qual cultura usar

SituaçãoCulturaExemplo
Mostrar para a pessoa que usa o programaA atual (não passe nada)$"{preco:C}"
Ler o que a pessoa digitouA atualdecimal.TryParse(texto, out decimal p)
Gravar ou ler arquivos, CSV, dados para outros programasCultureInfo.InvariantCulturenota.ToString(CultureInfo.InvariantCulture)
Dados de um país específiconew CultureInfo("en-US")decimal.Parse(texto, eua)
Comparar nomes de arquivo, extensões, códigosOrdinalStringComparison.OrdinalIgnoreCase
Ordenar nomes de pessoas para mostrarA atualnomes.Order()

Quando dá errado

O 3.5 virou 35 (e ninguém avisou)
string[] textos = ["8.5", "7.0", "9.5"];
double soma = 0;
foreach (string t in textos)
{
    soma += double.Parse(t);
}
Console.WriteLine($"Média: {soma / textos.Length}");
$ dotnet run media.cs
Média: 83,33333333333333
$ LANG=en_US.UTF-8 dotnet run media.cs
Média: 8.333333333333334

O que aconteceu: no computador da Ana, em pt-BR, o ponto de 8.5 é lido como separador de milhar, e as notas viram 85, 70 e 95. Num computador americano, o mesmo programa acerta. É o erro "funciona na minha máquina" clássico: não há exceção, só um resultado errado.

Como resolver: diga o formato do texto: double.Parse(t, CultureInfo.InvariantCulture), com using System.Globalization;. A média passa a ser 8,333333333333334 nos dois computadores (mostrada com vírgula aqui e com ponto lá).

FormatException: o texto no formato do outro país

A Ana esqueceu de passar a cultura americana no fornecedor.cs e trocou decimal.Parse(campos[1], eua) por decimal.Parse(campos[1]):

$ dotnet run fornecedor.cs
Unhandled exception. System.FormatException: The input string '1,299.90' was not in a correct format.
   at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
   at System.Number.ParseDecimal[TChar](ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)
   at Program.<Main>$(String[] args) in /home/ana/cs/fornecedor.cs:line 8

O que aconteceu: "O texto não está num formato correto". Em pt-BR, 1,299.90 teria a vírgula decimal antes de um ponto de milhar, o que não faz sentido. O mesmo acontece ao contrário: double.Parse("1.234,5", CultureInfo.InvariantCulture) lança The input string '1.234,5' was not in a correct format.

Como resolver: descubra de que país (ou de que programa) vem o texto e passe a cultura certa. Se o texto pode vir errado, use TryParse e avise o usuário.

O CSV ganhou colunas a mais
double[] notas = [8.5, 7, 9.25];
Console.WriteLine(string.Join(",", notas));
$ dotnet run juntar.cs
8,5,7,9,25

O que aconteceu: o string.Join transformou cada número em texto com a cultura atual, e as vírgulas decimais se misturaram com as vírgulas que separam os itens. Quem ler essa linha vai achar cinco números: 8, 5, 7, 9 e 25.

Como resolver: transforme os números com a cultura invariante antes de juntar: string.Join(",", notas.Select(n => n.ToString(CultureInfo.InvariantCulture))) dá 8.5,7,9.25 (o Select é da aula Filtrar e Projetar). Ou use ; como separador.


Referência rápida

CódigoO que faz
using System.Globalization;Traz CultureInfo e NumberStyles
CultureInfo.CurrentCultureA cultura atual (no Linux, vem do LANG)
CultureInfo.InvariantCultureA cultura que não muda: ponto decimal, igual em todo lugar
new CultureInfo("en-US") / CultureInfo.GetCultureInfo("en-US")A cultura de um país
x.ToString(cultura) / x.ToString("N2", cultura)Número ou data → texto, com a cultura escolhida
string.Create(cultura, $"…")Interpolação com a cultura escolhida
double.Parse(t, CultureInfo.InvariantCulture)Lê "3.5" como três e meio
double.TryParse(t, NumberStyles.Float, cultura, out double x)A versão segura, com cultura
CultureInfo.CurrentCulture = new CultureInfo("en-US");Muda a cultura do programa daqui em diante
c.NumberFormat.NumberDecimalSeparator, CurrencySymbolAs regras de números da cultura
StringComparison.OrdinalIgnoreCase / CurrentCultureIgnoreCaseComparar textos por código / pelas regras da língua

Exercícios

1. A cultura que não muda

Complete para o número sair com ponto em qualquer computador:

string texto = nota.ToString(CultureInfo.);

2. Os Estados Unidos

Complete o nome da cultura do inglês dos Estados Unidos:

CultureInfo eua = new CultureInfo("");

3. Quanto vale?

Num computador em pt-BR, o que Console.WriteLine(double.Parse("2.5")) mostra?

4. De onde o .NET tira a cultura atual no Linux?

5. O programa grava preços num arquivo que será lido por um sistema de outra empresa. Qual cultura usar ao gravar?

6. Para comparar a extensão de um arquivo (.JPG e .jpg), qual comparação é a indicada?

Missão final: o pedido do fornecedor

Missão: o pedido.txt (na outra aba) veio de um fornecedor dos Estados Unidos, com os preços no formato de lá. Rode e veja a FormatException. Conserte criando a cultura new CultureInfo("en-US") e passando-a para o decimal.Parse. A saída deve ser:

Notebook: R$ 1.299,90
Mouse: R$ 49,90
Total: R$ 1.349,80
using System.Globalization;

decimal total = 0;
foreach (string linha in File.ReadAllLines("pedido.txt"))
{
    string[] campos = linha.Split(';');
    decimal preco = decimal.Parse(campos[1]);
    Console.WriteLine($"{campos[0]}: {preco:C}");
    total += preco;
}
Console.WriteLine($"Total: {total:C}");
Notebook;1,299.90
Mouse;49.90
using System.Globalization;

CultureInfo eua = new CultureInfo("en-US");
decimal total = 0;
foreach (string linha in File.ReadAllLines("pedido.txt"))
{
    string[] campos = linha.Split(';');
    decimal preco = decimal.Parse(campos[1], eua);
    Console.WriteLine($"{campos[0]}: {preco:C}");
    total += preco;
}
Console.WriteLine($"Total: {total:C}");

Se quiser ir além: mostre também o total no formato do fornecedor, com total.ToString("C", eua), que dá $1,349.80 (só o desenho muda; o valor continua em reais).


Resumo

  • A cultura decide como números e datas viram texto e como texto vira número: separadores, moeda, formato de data, nomes de dias e meses, ordem alfabética.
  • A cultura atual (CultureInfo.CurrentCulture) vem do sistema; no Linux, da variável LANG. No computador da Ana, pt-BR: 3,5 e R$ 1.234,50.
  • Para pessoas, use a cultura atual. Para arquivos e outros programas, CultureInfo.InvariantCulture: x.ToString(CultureInfo.InvariantCulture) dá 3.5 em qualquer computador.
  • Em pt-BR, double.Parse("3.5") dá 35, sem erro. Para ler texto com ponto: double.Parse("3.5", CultureInfo.InvariantCulture).
  • Dados de um país: new CultureInfo("en-US") no Parse ou no ToString. Ler com uma cultura e mostrar com outra é normal.
  • Textos: Order() segue a língua; StringComparison.OrdinalIgnoreCase é para nomes de arquivo e códigos.