C# Operadores
Operadores são os símbolos que fazem contas e comparações: +, ==, &&, ??… Esta página traz todos os operadores do C# 14 em tabelas: primeiro a precedência (quem é calculado primeiro numa expressão com vários), depois cada grupo, com exemplo e resultado. Para aprender com calma, comece por Operadores Aritméticos e Precedência.
Todos os resultados vêm dos programas desta página, rodados no dotnet 10.0.112 da máquina da Ana (pasta ~/cs/op, cultura pt-BR) e no editor do guia, que deram a mesma saída.
Atalhos: Precedência · Aritméticos · Atribuição · ++ e -- · Comparação · Lógicos · Bits · null · Tipos · Índices e intervalos · Outros · Armadilhas · Experimente
Tabela de precedência e associatividade
Numa expressão com vários operadores, os de cima da tabela são calculados antes dos de baixo, como a multiplicação antes da soma na escola. Na mesma linha, a associatividade decide: "esquerda → direita" quer dizer que 10 - 4 - 3 é (10 - 4) - 3; "direita → esquerda" quer dizer que a = b = 7 é a = (b = 7).
| # | Grupo | Operadores | Associatividade |
|---|---|---|---|
| 1 | Primários | x.y f(x) a[i] x?.y x?[i] x++ x-- x! new typeof checked unchecked default nameof delegate sizeof stackalloc x->y | esquerda → direita |
| 2 | Unários | +x -x !x ~x ++x --x ^x (T)x await &x *x true false | direita → esquerda |
| 3 | Intervalo | x..y | (não se encadeia) |
| 4 | switch e with | x switch { … } x with { … } | esquerda → direita |
| 5 | Multiplicativos | x * y x / y x % y | esquerda → direita |
| 6 | Aditivos | x + y x - y | esquerda → direita |
| 7 | Deslocamento | x << y x >> y x >>> y | esquerda → direita |
| 8 | Relacionais e de tipo | x < y x > y x <= y x >= y is as | esquerda → direita |
| 9 | Igualdade | x == y x != y | esquerda → direita |
| 10 | E (lógico ou de bits) | x & y | esquerda → direita |
| 11 | OU exclusivo | x ^ y | esquerda → direita |
| 12 | OU (lógico ou de bits) | x | y | esquerda → direita |
| 13 | E condicional | x && y | esquerda → direita |
| 14 | OU condicional | x || y | esquerda → direita |
| 15 | Coalescência nula | x ?? y | direita → esquerda |
| 16 | Condicional (ternário) | c ? a : b | direita → esquerda |
| 17 | Atribuição e lambda | = += -= *= /= %= &= |= ^= <<= >>= >>>= ??= => | direita → esquerda |
Veja a tabela funcionando. Cada linha mostra a expressão e o resultado:
// precedencia.cs — quem é calculado primeiro
int a, b;
string? nada = null, vazio = null;
Console.WriteLine($"2 + 3 * 4 = {2 + 3 * 4}");
Console.WriteLine($"(2 + 3) * 4 = {(2 + 3) * 4}");
Console.WriteLine($"10 - 4 - 3 = {10 - 4 - 3}");
Console.WriteLine($"100 / 10 / 5 = {100 / 10 / 5}");
Console.WriteLine($"2 * 3 % 4 = {2 * 3 % 4}");
Console.WriteLine($"-2 * -3 = {-2 * -3}");
Console.WriteLine($"1 << 2 + 1 = {1 << 2 + 1}");
Console.WriteLine($"8 > 5 == true = {8 > 5 == true}");
Console.WriteLine($"true || false && false = {true || false && false}");
Console.WriteLine($"(true || false) && false = {(true || false) && false}");
Console.WriteLine($"6 & 3 | 8 = {6 & 3 | 8}");
Console.WriteLine($"nada ?? vazio ?? \"padrão\" = {nada ?? vazio ?? "padrão"}");
Console.WriteLine($"5 > 3 ? \"sim\" : \"não\" = {(5 > 3 ? "sim" : "não")}");
a = b = 7;
Console.WriteLine($"a = b = 7 → a = {a}, b = {b}");
a = 10;
a += b *= 2;
Console.WriteLine($"a += b *= 2 → a = {a}, b = {b}");
int i = 5;
int r = i++ + ++i;
Console.WriteLine($"i++ + ++i (i=5) → {r}, e i = {i}");
int[] v = [10, 20, 30];
Console.WriteLine($"v[^1] * 2 = {v[^1] * 2}");
Console.WriteLine($"-v[0] + v[1] = {-v[0] + v[1]}");
$ dotnet run precedencia.cs 2 + 3 * 4 = 14 (2 + 3) * 4 = 20 10 - 4 - 3 = 3 100 / 10 / 5 = 2 2 * 3 % 4 = 2 -2 * -3 = 6 1 << 2 + 1 = 8 8 > 5 == true = True true || false && false = True (true || false) && false = False 6 & 3 | 8 = 10 nada ?? vazio ?? "padrão" = padrão 5 > 3 ? "sim" : "não" = sim a = b = 7 → a = 7, b = 7 a += b *= 2 → a = 24, b = 14 i++ + ++i (i=5) → 12, e i = 7 v[^1] * 2 = 60 -v[0] + v[1] = 10
| Expressão | É lida como | Resultado | Regra |
|---|---|---|---|
2 + 3 * 4 | 2 + (3 * 4) | 14 | * (5) antes de + (6) |
10 - 4 - 3 | (10 - 4) - 3 | 3 | esquerda → direita |
100 / 10 / 5 | (100 / 10) / 5 | 2 | esquerda → direita (não é 100 / 2) |
2 * 3 % 4 | (2 * 3) % 4 | 2 | * e % estão na mesma linha: esquerda → direita |
1 << 2 + 1 | 1 << (2 + 1) | 8 | + (6) antes de << (7) |
8 > 5 == true | (8 > 5) == true | True | > (8) antes de == (9) |
true || false && false | true || (false && false) | True | && (13) antes de || (14) |
6 & 3 | 8 | (6 & 3) | 8 | 10 | & (10) antes de | (12) |
nada ?? vazio ?? "padrão" | nada ?? (vazio ?? "padrão") | padrão | direita → esquerda: o primeiro que não é null |
a = b = 7 | a = (b = 7) | os dois valem 7 | direita → esquerda: a atribuição devolve o valor atribuído |
a += b *= 2 | a += (b *= 2) | a = 24, b = 14 | direita → esquerda. Funciona, mas não escreva assim |
i++ + ++i (com i = 5) | 5 + 7 | 12 | os operandos são calculados da esquerda para a direita: i++ dá 5 (e i vira 6), ++i dá 7. No C# isso é garantido; mesmo assim, evite |
-v[0] + v[1] | (-(v[0])) + v[1] | 10 | v[0] (1) antes do - unário (2) |
Ninguém decora as 17 linhas. Os parênteses não custam nada e deixam a intenção clara para quem lê: (a && b) || c é melhor que a && b || c, mesmo que os dois sejam iguais. Basta lembrar três coisas: * / % antes de + -; comparações antes de && e ||; e a atribuição por último. Aula: Precedência.
As tabelas de grupo a seguir usam este programa. Cada linha da saída corresponde a uma tabela:
// opers.cs — um exemplo de cada grupo
int x = 17, y = 5;
Console.WriteLine($"{x + y} {x - y} {x * y} {x / y} {x % y} {-x}");
Console.WriteLine($"{17.0 / 5} {17m / 5} {-17 / 5} {-17 % 5}");
int c = 10;
c += 5; Console.Write($"{c} ");
c -= 3; Console.Write($"{c} ");
c *= 2; Console.Write($"{c} ");
c /= 4; Console.Write($"{c} ");
c %= 4; Console.WriteLine(c);
int n = 5;
Console.WriteLine($"{n++} {n} {++n} {n--} {--n}");
Console.WriteLine($"{x == y} {x != y} {x < y} {x > y} {x <= 17} {x >= 18}");
bool t = true, f = false;
Console.WriteLine($"{t && f} {t || f} {!t} {t & f} {t | f} {t ^ f} {t ^ t}");
Console.WriteLine($"{6 & 3} {6 | 3} {6 ^ 3} {~6} {1 << 4} {-16 >> 2} {-16 >>> 28}");
string? nome = null;
Console.WriteLine($"[{nome?.Length}] [{nome ?? "sem nome"}] [{nome?.ToUpper() ?? "?"}]");
nome ??= "Ana";
Console.WriteLine($"{nome} {nome!.Length}");
object o = "texto";
Console.WriteLine($"{o is string} {o is int} {(o as string)?.Length} {typeof(string).Name} {sizeof(long)} {nameof(nome)}");
Console.WriteLine($"{(int)3.7} {(double)7 / 2} {default(int)}");
int[] v = [10, 20, 30, 40, 50];
Console.WriteLine($"{v[^1]} {v[^2]} {string.Join(",", v[1..3])} {string.Join(",", v[..2])} {string.Join(",", v[3..])}");
Func<int, int> dobro = k => k * 2;
Console.WriteLine(dobro(21));
string tamanho = x switch { < 10 => "pequeno", < 100 => "médio", _ => "grande" };
Console.WriteLine(tamanho);
$ dotnet run opers.cs 22 12 85 3 2 -17 3,4 3,4 -3 -2 15 12 24 6 2 5 6 7 7 5 False True False True True False False True False False True True False 2 7 5 -7 16 -4 15 [] [sem nome] [?] Ana 3 True False 5 String 8 nome 3 3,5 0 50 40 20,30 10,20 40,50 42 médio
Aritméticos
| Operador | Nome | Exemplo (x = 17, y = 5) | Resultado | Aula |
|---|---|---|---|---|
+ | soma (e junta textos) | x + y | 22 | Operadores Aritméticos |
- | subtração; com um operando só, troca o sinal | x - y · -x | 12 · -17 | |
* | multiplicação | x * y | 85 | |
/ | divisão. Entre inteiros, corta a parte decimal | x / y · 17.0 / 5 · 17m / 5 · -17 / 5 | 3 · 3,4 · 3,4 · -3 | |
% | resto da divisão (com o sinal do primeiro) | x % y · -17 % 5 | 2 · -2 | |
+x | mais unário (não muda nada) | +x | 17 | Operadores Aritméticos |
Inteiro dividido por zero lança DivideByZeroException; double dividido por zero dá ∞ ou NaN. Detalhes em Tipos e Limites.
Atribuição
| Operador | É o mesmo que | Exemplo (começando com c = 10) | Resultado | Aula |
|---|---|---|---|---|
= | guarda o valor | c = 10 | 10 | Variáveis |
+= | c = c + 5 | c += 5 | 15 | Operadores de Atribuição |
-= | c = c - 3 | c -= 3 | 12 | |
*= | c = c * 2 | c *= 2 | 24 | |
/= | c = c / 4 | c /= 4 | 6 | |
%= | c = c % 4 | c %= 4 | 2 | |
&= |= ^= <<= >>= >>>= | c = c & … etc. | flags |= 4 | liga o bit 4 | Operadores de Atribuição |
??= | if (c is null) c = … | nome ??= "Ana" | só atribui se for null | Operadores ??, ?. e ??= |
++ e --
| Forma | O que faz | Exemplo (em sequência, começando com n = 5) | Mostra | Depois, n vale |
|---|---|---|---|---|
n++ | usa o valor, depois soma 1 | n++ | 5 | 6 |
++n | soma 1, depois usa | ++n | 7 | 7 |
n-- | usa, depois subtrai 1 | n-- | 7 | 6 |
--n | subtrai 1, depois usa | --n | 5 | 5 |
Sozinho numa linha (n++;), tanto faz a forma. A diferença só aparece quando o valor é usado na mesma expressão. Aula: ++ e --.
Comparação
| Operador | Pergunta | Exemplo (x = 17, y = 5) | Resultado | Aula |
|---|---|---|---|---|
== | é igual? | x == y | False | Operadores de Comparação |
!= | é diferente? | x != y | True | |
< | é menor? | x < y | False | |
> | é maior? | x > y | True | |
<= | é menor ou igual? | x <= 17 | True | |
>= | é maior ou igual? | x >= 18 | False |
Para textos, == compara o conteúdo ("ana" == "Ana" é False); para ignorar maiúsculas, string.Equals(a, b, StringComparison.OrdinalIgnoreCase) (veja string e StringBuilder). Para objetos de classe, == compara se é o mesmo objeto, a não ser que a classe defina outra coisa (records comparam os campos). Aula: A Classe object.
Lógicos (com bool)
| Operador | Nome | Verdadeiro quando… | Exemplo (t = true, f = false) | Resultado | Aula |
|---|---|---|---|---|---|
&& | E | os dois são verdadeiros. Se o primeiro é falso, nem olha o segundo | t && f | False | Operadores Lógicos |
|| | OU | pelo menos um é verdadeiro. Se o primeiro é verdadeiro, nem olha o segundo | t || f | True | |
! | NÃO | o valor é falso | !t | False | |
& | E sem atalho | como &&, mas sempre calcula os dois lados | t & f | False | Operadores Lógicos |
| | OU sem atalho | como ||, sempre calculando os dois | t | f | True | |
^ | OU exclusivo | exatamente um é verdadeiro | t ^ f · t ^ t | True · False |
O atalho do && é o que torna seguro escrever texto != null && texto.Length > 0: se texto é null, o Length nem é lido.
Bits (com inteiros)
| Operador | Nome | Exemplo | Em binário | Resultado |
|---|---|---|---|---|
& | E bit a bit | 6 & 3 | 110 & 011 = 010 | 2 |
| | OU bit a bit | 6 | 3 | 110 | 011 = 111 | 7 |
^ | OU exclusivo bit a bit | 6 ^ 3 | 110 ^ 011 = 101 | 5 |
~ | inverte todos os bits | ~6 | (complemento de dois) | -7 |
<< | desloca para a esquerda (× 2 a cada casa) | 1 << 4 | 1 → 10000 | 16 |
>> | desloca para a direita, mantendo o sinal | -16 >> 2 | — | -4 |
>>> C# 11 | desloca para a direita, enchendo com zeros | -16 >>> 28 | — | 15 |
Os operadores de bits aparecem em enums com [Flags], em permissões e em formatos de arquivo. Para o dia a dia, os lógicos com bool bastam.
Operadores de null
| Operador | O que faz | Exemplo (nome = null) | Resultado | Aula |
|---|---|---|---|---|
?. | acessa o membro só se não for null; senão, dá null | nome?.Length | null (mostrado vazio) | Operadores ??, ?. e ??= |
?[] | o mesmo para índices | lista?[0] | null se lista for null | |
?? | usa o da direita se o da esquerda for null | nome ?? "sem nome" · nome?.ToUpper() ?? "?" | sem nome · ? | |
??= | atribui só se for null | nome ??= "Ana" | nome vira "Ana" | |
x! | "confie em mim, não é null": só cala o aviso do compilador, não muda nada ao rodar | nome!.Length | 3 | Referências Anuláveis |
x?.y = valor C# 14 | atribui só se x não for null | cliente?.Nome = "Ana"; | veja abaixo | Operadores ??, ?. e ??= |
A atribuição com ?. é nova no C# 14 (antes, só dava para ler com ?.):
// C# 14: atribuição com ?.
Cliente? semCliente = null;
Cliente? cliente = new();
semCliente?.Nome = "Bruno"; // não faz nada: semCliente é null
cliente?.Nome = "Ana"; // atribui: cliente não é null
Console.WriteLine($"[{semCliente?.Nome}] [{cliente?.Nome}]");
class Cliente { public string Nome { get; set; } = ""; }
$ dotnet run nullassign.cs [] [Ana]
Operadores de tipo e conversão
| Operador | O que faz | Exemplo (object o = "texto") | Resultado | Aula |
|---|---|---|---|---|
is | testa o tipo (ou um padrão) | o is string · o is int | True · False | Pattern Matching |
as | converte, ou dá null se não der (só tipos de referência e anuláveis) | (o as string)?.Length | 5 | Polimorfismo |
(T)x | cast: converte, ou lança InvalidCastException | (int)3.7 · (double)7 / 2 | 3 · 3,5 | Conversão de Tipos |
typeof(T) | o Type de um tipo | typeof(string).Name | String | Reflexão |
sizeof(T) | bytes de um tipo de valor | sizeof(long) | 8 | Tipos e Limites |
nameof(x) | o nome, como texto | nameof(nome) | nome | throw |
default | o valor padrão | default(int) | 0 | Tipos e Limites |
Repare no (double)7 / 2: o cast (linha 2 da precedência) vem antes da divisão (linha 5). Primeiro o 7 vira 7.0, depois divide, e o resultado é 3,5. Já (double)(7 / 2) daria 3: a divisão de inteiros acontece antes.
Índices e intervalos
| Operador | O que faz | Exemplo (v = [10, 20, 30, 40, 50]) | Resultado | Aula |
|---|---|---|---|---|
a[i] | o item da posição i (do 0) | v[0] | 10 | Arrays |
^n C# 8 | contando do fim: ^1 é o último | v[^1] · v[^2] | 50 · 40 | Índices e Intervalos |
a..b C# 8 | do a (incluído) ao b (fora) | v[1..3] · v[..2] · v[3..] | 20,30 · 10,20 · 40,50 |
Outros operadores
| Operador | O que faz | Exemplo | Aula |
|---|---|---|---|
x.y | acessa um membro | nome.Length | Classes e Objetos |
f(x) | chama um método | Math.Max(3, 7) | Métodos |
new | cria um objeto | new List<int>() · new() | Construtores |
c ? a : b | "se c, então a, senão b" | idade >= 18 ? "maior" : "menor" | Operador Ternário |
=> | lambda (função sem nome) ou corpo de expressão | k => k * 2 (com 21 dá 42) | Lambdas |
x switch { … } C# 8 | escolhe um valor pelo padrão que casar | x switch { < 10 => "pequeno", < 100 => "médio", _ => "grande" } (com 17 dá médio) | Expressões switch |
x with { … } C# 9 | cópia de um record ou struct com mudanças | p with { Preco = 9.90m } | Records |
await | espera uma Task sem travar | await Task.Delay(1000) | async e await |
checked(…), unchecked(…) | estouro vira exceção, ou dá a volta | checked(a * b) | Inteiros |
+ com texto | junta (concatena) | "Olá, " + nome | Strings |
&x, *p, p->y | endereço e ponteiro (só em código unsafe) | int* p = &x; | Memória |
Suas classes podem definir o que +, ==, < e outros fazem com elas: aula Sobrecarga de Operadores.
Armadilhas
string nome = "Ana";
int idade = 29;
Console.WriteLine("Idade: " + idade + 1);
Console.WriteLine("Idade: " + (idade + 1));
Console.WriteLine(1 + 2 + " anos");
Console.WriteLine(nome + 1 + 2);
$ dotnet run e4.cs Idade: 291 Idade: 30 3 anos Ana12
O que aconteceu: + é calculado da esquerda para a direita. Em "Idade: " + idade + 1, primeiro vem "Idade: " + 29, que já é texto ("Idade: 29"); depois, texto + 1 emenda o 1. Em 1 + 2 + " anos", a conta 1 + 2 acontece antes de aparecer o texto.
Como consertar: parênteses na conta, (idade + 1), ou, melhor ainda, interpolação: $"Idade: {idade + 1}" (aula Interpolação).
| Código | Mensagem real (lab) | O que aconteceu e como consertar | Aula |
|---|---|---|---|
if (idade = 18) | error CS0029: Não é possível converter implicitamente tipo "int" em "bool" | = guarda; == compara. Escreva if (idade == 18). No C#, o erro aparece na compilação (em C, compilaria). | Comparação |
if (permissoes & 4 == 4) | error CS0019: O operador "&" não pode ser aplicado a operandos dos tipos "int" e "bool" | O == (linha 9) vem antes do & (linha 10): virou permissoes & (4 == 4). Escreva (permissoes & 4) == 4. | Precedência |
if (nota >= 0 && <= 10) | error CS1525: Termo de expressão inválido "<=" | Cada lado do && é uma comparação completa: nota >= 0 && nota <= 10. Ou, com padrões: nota is >= 0 and <= 10. | Pattern Matching |
int media = prova1 + prova2 / 2; | (compila, e a média sai errada) | A divisão vem antes da soma. Escreva (prova1 + prova2) / 2. Veja a missão abaixo. | Precedência |
7 / 2 esperando 3,5 | (compila e dá 3) | Entre inteiros, a divisão corta. Use 7.0 / 2, 7m / 2 ou (double)7 / 2. | Aritméticos |
0.1 + 0.2 == 0.3 | (compila e dá False) | O double não guarda 0,1 exato. Compare com tolerância, ou use decimal. | float, double e decimal |
Experimente
Missão: a Ana tirou 7 e 9 nas provas, mas o programa diz que a média é 11. Conserte a conta (sem mudar os números) para sair Média: 8.
int prova1 = 7;
int prova2 = 9;
int media = prova1 + prova2 / 2;
Console.WriteLine($"Média: {media}");
int prova1 = 7;
int prova2 = 9;
int media = (prova1 + prova2) / 2;
Console.WriteLine($"Média: {media}");
Experimente: tente prever cada resultado antes de rodar. Depois, tire e ponha parênteses e veja o que muda.
int a = 2, b = 3, c = 4;
Console.WriteLine(a + b * c);
Console.WriteLine((a + b) * c);
Console.WriteLine(a * b % c);
Console.WriteLine(a < b == b < c);
Console.WriteLine(a > b || b < c && c > 10);
Console.WriteLine("Total: " + a + b);
Console.WriteLine($"Total: {a + b}");
string? apelido = null;
Console.WriteLine(apelido ?? "sem apelido");
Resumo
- São 17 níveis de precedência. O essencial: primários (
x.y,f(),a[i],x++), unários (-x,!x, cast),* / %,+ -, comparações,==,&&,||,??,?:e, por último, a atribuição e o=>. - Quase todos vão da esquerda para a direita; a atribuição, o
??e o?:vão da direita para a esquerda. - Divisão entre inteiros corta;
%é o resto. &&e||param assim que sabem a resposta;&e|calculam os dois lados.?.,??e??=tratam onull; no C# 14,x?.y = valortambém atribui.- Na dúvida, parênteses.