C# tem tipagem estática: toda variável, campo e expressão tem um tipo conhecido em tempo de compilação. A linguagem tem um conjunto de tipos embutidos com palavras-chave próprias (int, double, string...). Cada palavra-chave é um apelido para um tipo do namespace System, então int e System.Int32 são o mesmo tipo, e string e System.String são o mesmo tipo.
Os tipos embutidos
| Tipo | Tipo .NET | Tamanho | Intervalo ou precisão | Literal |
|---|---|---|---|---|
bool | Boolean | 1 byte | true ou false | true |
byte | Byte | 1 byte | 0 a 255 | 200 |
sbyte | SByte | 1 byte | -128 a 127 | -5 |
short | Int16 | 2 bytes | -32.768 a 32.767 | 1200 |
ushort | UInt16 | 2 bytes | 0 a 65.535 | 60000 |
int | Int32 | 4 bytes | -2.147.483.648 a 2.147.483.647 | 42 |
uint | UInt32 | 4 bytes | 0 a 4.294.967.295 | 42u |
long | Int64 | 8 bytes | cerca de ±9,2 × 10^18 | 42L |
ulong | UInt64 | 8 bytes | 0 a cerca de 1,8 × 10^19 | 42ul |
float | Single | 4 bytes | cerca de ±3,4 × 10^38, ~6 a 9 dígitos | 2.5f |
double | Double | 8 bytes | cerca de ±1,8 × 10^308, ~15 a 17 dígitos | 2.5 ou 2.5d |
decimal | Decimal | 16 bytes | cerca de ±7,9 × 10^28, 28 a 29 dígitos | 2.5m |
char | Char | 2 bytes | uma unidade de código UTF-16, U+0000 a U+FFFF | 'A' |
string | String | referência | texto de qualquer tamanho, imutável | "hello" |
object | Object | referência | a base de todos os tipos | new object() |
Um literal inteiro sem sufixo é um int, ou o primeiro entre uint, long e ulong que consiga guardá-lo se for grande demais para um int, e um literal com ponto decimal é um double. É por isso que float f = 2.5; e decimal d = 2.5; não compilam: o literal é um double, e não existe conversão implícita de double para float ou decimal. Adicione o sufixo: 2.5f, 2.5m.
Todo tipo numérico expõe seus limites como constantes:
Saída:
byte: 0 to 255
short: -32768 to 32767
int: -2147483648 to 2147483647
long: -9223372036854775808 to 9223372036854775807
decimal max: 79228162514264337593543950335
double max: 1.798E+308
sizes: int 4, long 8, double 8, char 2
255 161 3000000000
Na prática, a maior parte do código usa quatro desses: int para contagens e índices, long para IDs e totais grandes, double para medições e matemática, e decimal para dinheiro.
decimal vs double
double (e float) guardam números em binário. A maioria das frações decimais, incluindo 0.1, não tem forma binária exata, então elas são guardadas como o valor representável mais próximo e pequenos erros se acumulam. decimal guarda um inteiro na base 10 mais uma escala, então 0.1 é exatamente 0.1.
Saída:
False
True
0.30000000000000004
0.3
double sum == 1.0: False
decimal sum == 1.0: True
Total: 59.97
Split three ways: 19.99
Use decimal para dinheiro, preços, impostos e qualquer coisa que uma pessoa vá somar à mão e comparar. Ele mantém os zeros à direita das entradas (1.50m é impresso como 1.50) e tem de 28 a 29 dígitos significativos.
Use double para medições físicas, estatística, geometria e jogos. Ele é várias vezes mais rápido, usa metade da memória e tem um intervalo enorme. Nunca compare dois doubles calculados com ==; compare a diferença com uma tolerância: Math.Abs(x - y) < 1e-9.
decimal não é mágico: 1m / 3m continua sendo 0.3333333333333333333333333333, e a aritmética com double não lança exceção na divisão por zero (produz infinito, ou NaN para 0.0 / 0.0), enquanto a divisão por zero de decimal e de inteiros lança uma DivideByZeroException.
Overflow de inteiros e checked
Aritmética inteira que passa do intervalo do tipo dá a volta por padrão, sem aviso. O compilador pega o overflow em expressões constantes, mas não quando os valores vêm de variáveis:
Saída:
-2147483648
OverflowException
-1294967296
2147483648
checked(...) ou um bloco checked { ... } transforma o overflow em uma OverflowException. Você também pode ativar isso para o projeto inteiro com <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>. Escrever int x = int.MaxValue + 1; com constantes é um erro de compilação (CS0220), porque o compilador consegue ver o overflow.
A última linha mostra a solução para a maioria dos bugs de overflow: converter para um tipo maior antes da aritmética. (long)(max + 1) estaria errado, já que a soma já deu a volta quando o cast executa.
Tipos de valor e tipos de referência
Todo tipo em C# é um tipo de valor ou um tipo de referência, e a diferença decide o que a atribuição faz.
- Tipos de valor: todos os tipos numéricos,
bool,char,DateTime, enums e todastruct. A variável contém os dados; a atribuição os copia. - Tipos de referência:
string, arrays,objecte todaclass. A variável contém uma referência para um objeto; a atribuição copia a referência, então as duas variáveis veem o mesmo objeto.
Saída:
a=10 b=99
first[0]=99
s1=cat s2=cats
string é um tipo de referência que, na prática, se comporta como um valor: ela é imutável, e == compara o texto, não as referências. Mais sobre tipos de valor definidos pelo usuário em structs.
char e bool
Um char é uma unidade de código UTF-16, escrita entre aspas simples. Por baixo, é um tipo numérico, então se converte para int e aceita aritmética:
Saída:
66
C
True
True
7
b
True
bool tem exatamente dois valores e não se converte de ou para números: if (count) é um erro de compilação, não um teste de diferente de zero. Escreva if (count != 0). Caracteres fora do Plano Multilíngue Básico, como a maioria dos emojis, ocupam dois valores char (um par substituto), então o Length de uma string conta unidades UTF-16, não caracteres visíveis.
Valores padrão
Campos, elementos de array e default(T) recebem o valor padrão do tipo: zero para números, false para bool, '\0' para char e null para tipos de referência. Variáveis locais não recebem padrão e precisam ser atribuídas antes do uso.
Saída:
0
0
False
True
0001-01-01
0,0,0
Desde o C# 7.1, você pode escrever o literal default sem o tipo quando o compilador consegue inferi-lo (int x = default;). Um tipo de valor não pode ser null, e é para isso que existe int? (um tipo de valor anulável); veja tipos anuláveis.
Inteiros do tamanho nativo e inteiros grandes
Mais dois tipos numéricos existem para casos especiais. nint e nuint (C# 9) são inteiros do tamanho de um ponteiro, 32 ou 64 bits conforme a plataforma, usados em interoperabilidade e código de baixo nível. System.Numerics.BigInteger guarda inteiros de qualquer tamanho, ao custo de velocidade:
using System.Numerics;
BigInteger factorial = 1;
for (int i = 2; i <= 30; i++) factorial *= i;
// 265252859812191058636308480000000
Perguntas frequentes
Quais são os tipos de dados em C#?
Os tipos embutidos são os inteiros (sbyte, byte, short, ushort, int, uint, long, ulong), os tipos de ponto flutuante float e double, decimal para aritmética decimal exata, char para um caractere UTF-16, bool, e os tipos de referência string e object. Cada palavra-chave é um apelido para um tipo do .NET: int é System.Int32, string é System.String.
Devo usar decimal ou double para dinheiro em C#?
Use decimal. Um double guarda frações binárias, então valores como 0.1 não podem ser representados exatamente e as somas se desviam (0.1 + 0.2 == 0.3 é false). Um decimal guarda dígitos na base 10, mantém de 28 a 29 dígitos significativos e dá os resultados que um contador espera. double é mais rápido e tem um intervalo muito maior, então use-o para ciência, gráficos e medições.
Qual é o valor máximo de int em C#?
int.MaxValue é 2.147.483.647 e int.MinValue é -2.147.483.648 (um inteiro de 32 bits com sinal). Somar 1 a int.MaxValue dá a volta sem aviso para int.MinValue, a menos que o código execute em um contexto checked, onde ele lança uma OverflowException. Use long (até cerca de 9,2 quintilhões) para valores maiores.
Qual a diferença entre float e double em C#?
float tem 32 bits e cerca de 6 a 9 dígitos significativos; double tem 64 bits e cerca de 15 a 17. Literais de ponto flutuante são double por padrão, então um float precisa do sufixo f: float speed = 2.5f;. Use double, a menos que a memória ou uma API (como o Vector3 da Unity) peça float.
Qual a diferença entre tipos de valor e tipos de referência em C#?
Uma variável de tipo de valor (int, double, bool, DateTime, qualquer struct) guarda os próprios dados, e a atribuição os copia. Uma variável de tipo de referência (string, arrays, qualquer class) guarda uma referência para um objeto no heap, e a atribuição copia a referência, então duas variáveis podem apontar para o mesmo objeto.