C# Conversão de Tipos

Nas aulas anteriores, o compilador reclamou várias vezes: "não é possível converter implicitamente tipo double em int". Nesta aula você vai entender essa regra. Algumas conversões o C# faz sozinho, porque nunca perdem nada. Outras você precisa pedir, com um cast, porque podem perder dados. Saber a diferença evita médias erradas, centavos sumidos e números que viram outros sem aviso.

Você vai aprender
  • As conversões implícitas, que o C# faz sozinho (int para long, double, decimal).
  • O cast explícito, (int)valor, e o que ele faz com a parte decimal (corta, não arredonda).
  • A perda de dados: (byte)300 dá 44, e como o checked transforma isso em OverflowException.
  • Cast × Math.Round × Convert.ToInt32.
  • A promoção nas contas: por que soma / quantidade dá 8 e (double)soma / quantidade dá 8,33.
  • Por que texto não vira número com cast (CS0030).

Todos os programas desta aula foram rodados com o .NET 10 (C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs.


A ideia: o copo e a jarra

Despejar um copo d'água numa jarra grande é sempre seguro: cabe tudo, não se perde nada. Já despejar a jarra cheia num copo pode derramar. Se alguém pedir isso, você vai querer ter certeza de que a pessoa sabe o que está fazendo.

O C# pensa igual:

  • de um tipo "menor" para um "maior" (int para long), a conversão é implícita: o C# faz sozinho, sem você escrever nada;
  • de um tipo "maior" para um "menor" (double para int), pode derramar, então a conversão tem de ser explícita: você escreve o tipo de destino entre parênteses, e isso se chama cast (em inglês, "molde").

1. Conversões implícitas: o C# faz sozinho

int pontos = 1500;
long total = pontos;
double media = pontos;
decimal saldo = pontos;
char letra = 'A';
int codigo = letra;
float f = 2.5f;
double d = f;
Console.WriteLine(total);
Console.WriteLine(media);
Console.WriteLine(saldo);
Console.WriteLine(codigo);
Console.WriteLine(d);
$ dotnet run implicita.cs
1500
1500
1500
65
2,5
long total = pontos;
Todo int cabe num long. Nenhuma palavra extra.
double media = pontos; · decimal saldo = pontos;
Um inteiro também entra num double ou num decimal: 1500 vira 1500,0, que aparece como 1500.
int codigo = letra;
Um char entra num int: vale o código do caractere, 65 (aula char e bool).
double d = f;
float cabe em double.

O caminho das conversões implícitas entre os números mais usados:

DeVira sozinho em
byteshort, ushort, int, uint, long, ulong, float, double, decimal
shortint, long, float, double, decimal
charushort, int, uint, long, ulong, float, double, decimal
intlong, float, double, decimal
longfloat, double, decimal
floatdouble
doublenenhum
decimalnenhum

Repare nas duas últimas linhas: double e decimal não viram nada sozinhos, nem um no outro. O double tem um alcance enorme (até 10308) e o decimal tem mais dígitos exatos; nenhum dos dois "cabe" inteiro no outro.

A exceção: long para double perde dígitos

Há um caso em que a conversão implícita perde algo: inteiros muito grandes num float ou double. O valor continua perto, mas os últimos dígitos se perdem:

long grande = 123456789012345678;
float f = grande;
double d = grande;
Console.WriteLine(grande);
Console.WriteLine(f);
Console.WriteLine(d);
Console.WriteLine((long)d);
$ dotnet run longfloat.cs
123456789012345678
1,2345679E+17
1,2345678901234568E+17
123456789012345680       « voltou diferente: …678 virou …680

O C# deixa porque o tamanho do número se mantém (só a precisão cai), e isso raramente importa. Mas é por isso que códigos, CPFs e números de cartão nunca devem ir para double.


2. Conversão explícita: o cast

Quando a conversão pode perder dados, o C# exige que você peça, escrevendo o tipo de destino entre parênteses, na frente do valor:

double nota = 7.9;
int inteiro = (int)nota;
Console.WriteLine(inteiro);
Console.WriteLine((int)-7.9);
long grande = 1000;
int menor = (int)grande;
Console.WriteLine(menor);
decimal preco = 19.99m;
int reais = (int)preco;
Console.WriteLine(reais);
double d = (double)preco;
Console.WriteLine(d);
decimal volta = (decimal)0.1;
Console.WriteLine(volta);
$ dotnet run explicita.cs
7          « 7,9 virou 7: cortou, não arredondou
-7
1000
19
19,99
0,1
(int)nota
O cast: "converta nota para int". De double para inteiro, a parte depois da vírgula é jogada fora: 7,9 vira 7, e -7,9 vira -7 (o corte é sempre em direção ao zero).
(int)grande
De long para int. 1000 cabe, então nada se perde. O compilador não olha o valor; olha os tipos, e um long poderia não caber.
(int)preco
De decimal para int: também corta, 19.
(double)preco · (decimal)0.1
Entre decimal e double, sempre com cast, nas duas direções.

Sem o cast, cada uma dessas linhas daria o erro que você já conhece:

long visitas = 1000;
int v = visitas;
Console.WriteLine(v);
$ dotnet run conv_erro_long.cs
/home/ana/cs/conv_erro_long.cs(2,9): error CS0266: Não é possível converter implicitamente tipo "long" em "int". Existe uma conversão explícita (há uma conversão ausente?)

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

Agora a mensagem faz sentido: "existe uma conversão explícita" = existe um cast; "há uma conversão ausente?" = "você esqueceu de escrever o cast?". Em inglês: Cannot implicitly convert type 'long' to 'int'. An explicit conversion exists (are you missing a cast?).


3. Quando o cast derrama

O cast é uma promessa: "eu sei que cabe". Se não couber, o C# não reclama. Ele faz o que der:

int grande = 300;
byte pequeno = (byte)grande;
Console.WriteLine(pequeno);
long enorme = 5_000_000_000;
int corte = (int)enorme;
Console.WriteLine(corte);
int negativo = -1;
uint semSinal = (uint)negativo;
Console.WriteLine(semSinal);
$ dotnet run perda.cs
44              « 300 num byte
705032704       « 5 bilhões num int
4294967295      « -1 num uint

Entre inteiros, o cast guarda só os bits que cabem e joga o resto fora. O byte tem 8 bits (0 a 255): 300 - 256 = 44. É o mesmo "dar a volta" do estouro (aula Inteiros). Nenhum erro, nenhum aviso: o programa segue com um número que não tem nada a ver com o original.

checked também vale para o cast

O mesmo checked da aula de inteiros transforma o "derramou" em erro:

int grande = 300;
byte pequeno = checked((byte)grande);
Console.WriteLine(pequeno);
$ dotnet run perdachecked.cs
Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
   at Program.<Main>$(String[] args) in /home/ana/cs/perdachecked.cs:line 2

Com decimal, a conferência é sempre ligada, mesmo sem checked: (int) de um decimal grande demais lança System.OverflowException: Value was either too large or too small for an Int32. ("o valor era grande ou pequeno demais para um Int32").

Valores fixos: o compilador confere

Se o valor é fixo no código, o compilador vê que não cabe e não compila. Um programa com Console.WriteLine((int)1e20); dá:

$ dotnet run doubleint.cs
/home/ana/cs/doubleint.cs(1,19): error CS0221: O valor de constante "1E+20" não pode ser convertido em "int" (use a sintaxe "unchecked" para substituir)

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Um double fora da faixa não tem resultado confiável

Com variáveis, o .NET 10 do laboratório converte (int) de 1e20 em 2147483647 (o maior int) e de double.NaN em 0. O editor do guia mostra o mesmo. Mas esse comportamento já mudou entre versões do .NET (até o .NET 8 o resultado dependia do processador: num PC comum, Intel ou AMD, dava -2147483648 nos dois casos). Não escreva programas que dependam dele: confira a faixa antes, ou use checked.


4. Cortar ou arredondar?

O cast corta. Quando você quer arredondar, há outras ferramentas:

double nota = 7.9;
Console.WriteLine((int)nota);
Console.WriteLine((int)Math.Round(nota));
Console.WriteLine(Convert.ToInt32(nota));
Console.WriteLine(Convert.ToInt32(2.5));
Console.WriteLine(Convert.ToInt32(3.5));
Console.WriteLine(Math.Round(2.5));
Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero));
Console.WriteLine((int)Math.Floor(7.9));
Console.WriteLine((int)Math.Ceiling(7.1));
$ dotnet run arredondar.cs
7
8
8
2          « 2,5 virou 2!
4          « 3,5 virou 4
2
3
7
8
(int)nota → 7
Corta a parte decimal.
(int)Math.Round(nota) → 8
Math.Round ("arredondar") devolve um double arredondado (8), e o cast o transforma em int sem perder nada.
Convert.ToInt32(nota) → 8
A classe Convert ("converter") arredonda em vez de cortar.
Convert.ToInt32(2.5) → 2 · Convert.ToInt32(3.5) → 4
A surpresa: quando o número está exatamente no meio (x,5), o .NET arredonda para o par mais próximo. É o "arredondamento do banqueiro", que evita que os arredondamentos puxem sempre para cima. Math.Round faz o mesmo.
MidpointRounding.AwayFromZero → 3
Para o arredondamento da escola (x,5 sobe), peça AwayFromZero, "para longe do zero".
Math.Floor · Math.Ceiling
"Piso" (arredonda sempre para baixo) e "teto" (sempre para cima). Detalhes em Matemática.

5. Conversões dentro das contas (promoção)

Quando uma conta mistura tipos, o C# converte o menor para o maior antes de calcular. Isso se chama promoção. E quando a conta tem só inteiros, ela é feita em inteiros, mesmo que o resultado vá para um double:

int soma = 7 + 8 + 10;
int quantidade = 3;
double media = soma / quantidade;
Console.WriteLine($"Média: {media}");
$ dotnet run notas.cs
Média: 8           « 25 / 3 devia dar 8,33…

soma / quantidade é int dividido por int: o resultado é o int 8 (a divisão inteira joga fora o resto, aula Operadores Aritméticos). Só depois o 8 é convertido para double. O conserto é converter um dos lados antes da divisão:

int a = 7;
int b = 2;
Console.WriteLine(a / b);
Console.WriteLine((double)a / b);
Console.WriteLine((double)(a / b));
Console.WriteLine(a / 2.0);
$ dotnet run promocao.cs
3
3,5
3
3,5
a / b → 3
Divisão inteira.
(double)a / b → 3,5
O cast vale só para o a, que vira 7,0. Agora é double dividido por int; o b é promovido a double, e a divisão é com vírgula.
(double)(a / b) → 3
Aqui os parênteses fazem a divisão inteira primeiro (3) e só depois convertem: 3,0. Tarde demais.
a / 2.0 → 3,5
Com um número fixo, basta escrevê-lo com ponto: 2.0 é double.

Missão: a média das notas 7, 8 e 10 sai Média: 8,00. Use um cast (double) em soma, antes da divisão, para mostrar Média: 8,33. (O :F2 dentro das chaves mostra o número com 2 casas; aula Formatar Números.)

int soma = 7 + 8 + 10;
int quantidade = 3;
double media = soma / quantidade;
Console.WriteLine($"Média: {media:F2}");
int soma = 7 + 8 + 10;
int quantidade = 3;
double media = (double)soma / quantidade;
Console.WriteLine($"Média: {media:F2}");

O tipo do resultado

ContaResultadoPor quê
int + longlongO int é promovido a long
int + doubledoubleO int é promovido a double
int * decimaldecimalO int é promovido a decimal
char + intintO char vira o seu código
short + short, byte + byteintContas com tipos menores que int são sempre feitas em int
decimal * doublenão compila (CS0019)Não há promoção entre os dois

Todos esses tipos foram conferidos com GetType() no laboratório.


6. Texto não vira número com cast

O cast só converte entre tipos "parentes", como os números entre si. Uma string não é parente de int:

string texto = "42";
int numero = (int)texto;
Console.WriteLine(numero);
$ dotnet run castcomstring.cs
/home/ana/cs/castcomstring.cs(2,14): error CS0030: Não é possível converter tipo "string" em "int"

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

Repare na diferença para o CS0266: aqui a mensagem não diz "implicitamente" nem "existe uma conversão explícita". O CS0030 quer dizer: não existe conversão nenhuma, nem com cast. O mesmo acontece com (bool)5 e (int)true. Para transformar texto em número existe int.Parse e int.TryParse, a próxima aula: Texto → Número.

O caminho contrário, de número para texto, é fácil: todo valor tem o método ToString():

int idade = 29;
string texto = idade.ToString();
Console.WriteLine(texto + "!");
double altura = 1.65;
Console.WriteLine(altura.ToString());
$ dotnet run convtostring.cs
29!
1,65

O ToString() usa a cultura do computador: 1,65 no Ubuntu da Ana. A aula Cultura mostra como escolher outra.


7. A classe Convert

Além do cast, o .NET tem a classe Convert, com um método para cada tipo de destino: Convert.ToInt32, Convert.ToDouble, Convert.ToBoolean, Convert.ToString… Ela é mais "esperta" que o cast em alguns pontos:

Console.WriteLine(Convert.ToInt32(true));
Console.WriteLine(Convert.ToBoolean(0));
Console.WriteLine(Convert.ToDouble(10));
Console.WriteLine(Convert.ToInt32(300.6));
Console.WriteLine(Convert.ToByte(300));
$ dotnet run convert1.cs
1
False
10
301
Unhandled exception. System.OverflowException: Value was either too large or too small for an unsigned byte.
   at System.Convert.ThrowByteOverflowException()
   at System.Convert.ToByte(Int32 value)
   at Program.<Main>$(String[] args) in /home/ana/cs/convert1.cs:line 5
  • Converte bool em número (true = 1) e número em bool (0 = false), o que o cast não faz.
  • Arredonda em vez de cortar (300,6 virou 301).
  • Confere sempre a faixa: Convert.ToByte(300) lança OverflowException em vez de dar 44.
Quero…Use
Guardar num tipo maiorNada: a conversão é implícita
Jogar fora a parte decimal(int)valor
Arredondar para inteiro(int)Math.Round(valor) ou Convert.ToInt32(valor)
Converter entre decimal e double(decimal)d, (double)m
Converter para um tipo menor, com erro se não couberchecked((byte)x) ou Convert.ToByte(x)
Fazer uma divisão com vírgula entre inteiros(double)a / b
Número para textovalor.ToString()
Texto para númeroint.Parse / int.TryParse (aula Texto → Número)

Quando dá errado

error CS0266: Não é possível converter implicitamente tipo "…" em "…". Existe uma conversão explícita (há uma conversão ausente?)

O que significa: a conversão pode perder dados (double para int, long para int, double para decimal), então o C# não a faz sozinho.

Como consertar: se você sabe que é seguro (ou quer mesmo cortar), escreva o cast: (int)valor. Se não, mude o tipo da variável de destino para um que caiba.

error CS0030: Não é possível converter tipo "string" em "int"

O que significa: não existe conversão entre os dois tipos, nem com cast.

Como consertar: de texto para número, int.Parse ou int.TryParse; de bool para número, ativo ? 1 : 0 ou Convert.ToInt32(ativo).

error CS0221: O valor de constante "1E+20" não pode ser convertido em "int" (use a sintaxe "unchecked" para substituir)

O que significa: um cast de um valor fixo que não cabe no tipo de destino.

Como consertar: use um tipo que caiba. O unchecked( … ) que a mensagem sugere só serve se você quer mesmo o valor "derramado".

A média (ou a porcentagem) saiu sem a parte decimal

O que significa: a divisão foi feita entre dois inteiros, antes de o resultado ir para o double.

Como consertar: (double)soma / quantidade, com o cast dentro da conta, num dos lados. (double)(soma / quantidade) não resolve.

Um número virou outro sem erro (44, 705032704, 4294967295)

O que significa: um cast de inteiro para um tipo menor (ou sem sinal) em que o valor não cabia.

Como consertar: use checked((byte)x) ou Convert.ToByte(x) para ter OverflowException, ou um tipo maior.

System.OverflowException: Value was either too large or too small for an Int32.

O que significa: uma conversão conferida (de decimal, com Convert ou dentro de checked) recebeu um valor fora da faixa do tipo.

Como consertar: confira o valor antes, ou use um tipo maior (long).


Referência rápida

EscritaO que fazExemplo
long l = i;Implícita (do menor para o maior)int → long, double, decimal
(int)xCast explícito; com vírgula, corta(int)7.9 = 7, (int)-7.9 = -7
(byte)300 (com variável)Inteiros: guarda os bits que cabem44
checked((byte)x)Cast conferidoOverflowException
(double)a / bDivisão com vírgula entre inteiros7 / 2 = 3; (double)7 / 2 = 3,5
Math.Round(x)Arredonda (x,5 vai para o par)Math.Round(2.5) = 2
Math.Round(x, MidpointRounding.AwayFromZero)Arredonda como na escola2,5 → 3
Convert.ToInt32(x)Arredonda e confere a faixaConvert.ToInt32(7.9) = 8
x.ToString()Número para texto"1,65"

Exercícios

1. O cast

Complete para converter o double em int:

int inteiro = nota;

2. Cortar

Quanto vale (int)9.99?

3. Derramou

Com int x = 260;, quanto vale (byte)x?

4. Qual linha compila sem cast?

5. Com int a = 9, b = 2;, qual expressão vale 4,5?

6. int n = (int)"15"; O que acontece?

7. O que Convert.ToInt32(4.5) devolve?

Missão final: reais e centavos

Missão: o programa quer separar o preço R$ 19,99 em reais e centavos, mas não compila: um decimal não entra sozinho num int (CS0266). Use o cast (int) nas duas linhas (na segunda, em volta da conta inteira, com parênteses) para mostrar 19 reais e 99 centavos.

decimal preco = 19.99m;
int reais = preco;
int centavos = (preco - reais) * 100;
Console.WriteLine($"{reais} reais e {centavos} centavos");
decimal preco = 19.99m;
int reais = (int)preco;
int centavos = (int)((preco - reais) * 100);
Console.WriteLine($"{reais} reais e {centavos} centavos");

Na solução, (int)preco corta para 19; preco - reais é 0,99 (um decimal, exato); vezes 100 dá 99,00; e o (int) de fora faz dele o inteiro 99. Com double no lugar de decimal, a mesma conta dá 98 centavos no laboratório: 19,99 não é exato em double, a diferença vira 0,98999…, e o cast corta. Mais um motivo para dinheiro ser decimal.


Resumo

  • Implícita: do tipo menor para o maior, o C# converte sozinho (int → long, double, decimal; char → int).
  • Explícita (cast): (int)x, quando pode perder dados. Sem o cast: CS0266.
  • De número com vírgula para inteiro, o cast corta: (int)7.9 = 7. Para arredondar: Math.Round ou Convert.ToInt32 (x,5 vai para o par).
  • Entre inteiros, um cast que não cabe dá a volta sem aviso ((byte)300 = 44). checked e Convert lançam OverflowException.
  • Contas com tipos misturados promovem o menor; só com inteiros, a divisão é inteira. Use (double)soma / quantidade.
  • Texto e número não se convertem com cast (CS0030): número → texto com ToString(), texto → número com Parse/TryParse.