Expressões em C#

Tip

Este artigo faz parte da secção Fundamentos para programadores que já conhecem pelo menos uma linguagem de programação e estão a aprender C#. Se és novo na programação, começa primeiro pelos tutoriais para começar .

Vem de outra língua? As expressões em C# funcionam de forma semelhante a Java, C++ e JavaScript. Uma diferença a notar: operadores de atribuição composta como += e o operador ++ de incremento são expressões em C#, pelo que podem aparecer em expressões maiores.

Uma expressão é um pedaço de código que o compilador avalia para produzir um valor — um número, uma cadeia, uma referência ou um bool. Por exemplo, 3 + 4 * 2 é uma expressão que é avaliada como o inteiro 11, e total > 10 é uma expressão que é avaliada como true ou false.

As expressões mais simples são literais (como 42 ou "hello") e nomes de variáveis (como total). Constroem expressões mais complexas combinando expressões mais simples com operadores.

Expressões e declarações

Uma expressão produz um valor. Uma declaração é uma instrução completa que o programa executa. Muitas afirmações contêm expressões. Por exemplo, int total = 3 + 4 * 2; é uma declaração de variável. O compilador avalia a expressão 3 + 4 * 2inicializadora , que produz 11, e atribui esse valor à nova variável total.

Podes pensar numa expressão como uma frase e numa afirmação em C# como uma frase completa. Estas definições de trabalho ajudam-no a compreender a terminologia formal e as explicações que encontrará à medida que aprende mais sobre C#, embora a programação comum raramente exija que lembre conscientemente a distinção.

Combinação de expressões

As expressões podem ser combinadas. Considere 3 + 4 * 2. Esta única expressão contém na verdade duas expressões menores: a expressão 4 * 2de multiplicação e a expressão3 + <result> de adição. Quando as expressões são combinadas, C# precisa de uma regra para decidir qual delas avaliar primeiro. Essa regra é a precedência do operador.

Não precisas de memorizar toda a hierarquia de precedência. A secção seguinte resume os grupos do dia a dia, e os parênteses permitem sempre que a ordem seja explícita quando tem dúvidas.

Precedência dos operadores

A precedência do operador determina como uma expressão combinada se agrupa em subexpressões. Este conceito é semelhante às regras de ordem de operações da aula de matemática. Um operador com precedência superior liga-se mais apertadamente aos seus operandos. A expressão está estruturada como se esses operandos estivessem entre parênteses. Em 3 + 4 * 2, * tem precedência superior à +, pelo que a expressão agrupa como 3 + (4 * 2), não (3 + 4) * 2.

C# tem mais grupos de precedência do que os quatro aqui resumidos. Esses grupos adicionais impõem regras familiares — por exemplo, multiplicação antes da adição — e abrangem o conjunto completo de operadores. Os seguintes grupos cobrem aquilo que mais frequentemente encontra no código do dia a dia:

  1. Primário, unário e de alcance — são três grupos de precedência distintos, todos eles ligados de forma mais apertada do que a aritmética. Este resumo junta-os numa só etapa porque, para fins práticos, todos se aplicam antes da aritmética.
    • Os operadores primários — acesso membro (x.y), chamadas de método (f()), indexação (a[i]) e acesso condicional nulo (?., ?[]) — vinculam de forma mais apertada de todos.
    • Operadores unários atuam sobre um único operando: negação (-x), lógica NOT (!flag), incremento prefixo (++i) e incremento pós-fixo (i++).
    • O operador de intervalo (..) constrói intervalos de índice para expressões de fatia, como array[1..4].
  2. Aritmética*, /, % liga mais apertadamente do que + e -. A multiplicação e a divisão acontecem antes da adição e subtração.
  3. Comparação<, >, <=, >=, ==, != têm menor precedência do que os operadores aritméticos, pelo que as operações aritméticas são efetuadas antes da comparação.
  4. Lógico&& e || ligam o menos apertado dos operadores comuns, por isso as comparações são concluídas antes da combinação lógica.

Esta precedência significa que a expressão score + bonus > threshold && attempts < maxAttempts avalia exatamente como a leria: somar score e bonus, comparar a soma com threshold, comparar attempts com maxAttempts, e depois combinar os dois bool resultados com &&. Adicionar parênteses para tornar cada agrupamento explícito mostra a mesma estrutura: ((score + bonus) > threshold) && (attempts < maxAttempts).

Para a hierarquia completa de precedência que cobre todos os operadores, veja operadores e expressões C#.

Use parênteses para deixar a intenção clara

Parênteses sobrepõem-se à precedência e documentam a sua intenção ao mesmo tempo. Quando a ordem não for óbvia nos grupos acima, adicione parênteses:

int score = 80;
int bonus = 15;
int threshold = 90;
bool eligible = true;

// Without parentheses: || binds less tightly than &&, so this reads as:
//   eligible && (score + bonus > threshold) || bonus > 20
bool result1 = eligible && score + bonus > threshold || bonus > 20;

// With parentheses: forces the || to combine two complete conditions
bool result2 = (eligible && score + bonus > threshold) || bonus > 20;

Console.WriteLine(result1); // => True
Console.WriteLine(result2); // => True

// Parentheses can also change the result:
bool isAdmin = false;
bool isOwner = true;

// Without: && binds tighter, so: (isAdmin && isOwner) || true
bool access1 = isAdmin && isOwner || true;
// With: forces the || to run first
bool access2 = isAdmin && (isOwner || true);

Console.WriteLine(access1); // => True   (false && true is false; false || true is true)
Console.WriteLine(access2); // => False  (false && anything is false)

As duas últimas linhas mostram que parênteses podem alterar o resultado, não apenas o estilo. Quando && e || aparecerem juntos, adiciona parênteses para indicar qual condição se combina primeiro. Um leitor que vê (isAdmin && isOwner) || isSuperUser percebe imediatamente a intenção.

Como as expressões são avaliadas

Compreender como C# avalia expressões combinadas é mais fácil com uma analogia. Pense em resolver um problema matemático complexo com lápis e papel: identifica a subexpressão mais interna ou de maior precedência, calcula o seu valor intermédio, escreve o resultado e repete com a próxima subexpressão — continuando até chegar à resposta final.

C# segue o mesmo processo, guiado por duas regras:

Regra 1: Os operandos são avaliados da esquerda para a direita. Para qualquer expressão binária, ambos os operandos devem ser totalmente avaliados antes de o operador ser aplicado. C# avalia primeiro o operando esquerdo, depois o direito, e depois executa a operação.

Regra 2: Alguns operadores fazem curto-circuito. Certos operadores deixam de avaliar assim que o resultado é determinado, saltando quaisquer operandos restantes:

  • && (condicional AND): retorna false assim que o lado esquerdo for false. O lado direito nunca é avaliado.
  • || (OR condicional): retorna true assim que o lado esquerdo for true. O lado direito nunca é avaliado.
  • ?: (condicional/ternário): avalia apenas o ramo que corresponde à condição — o outro ramo nunca é avaliado.
  • ?. (acesso de membro condicional nulo) e ?[] (acesso de elemento condicional nulo): param e retornam null imediatamente quando o lado esquerdo é null, saltando o acesso ao membro ou índice.
  • ??= (atribuição de coalescimento nulo): atribui o lado direito apenas quando o lado esquerdo é null.

Avaliação em papel e lápis

Considere a expressão 3 + 6 / 2. Embora a adição apareça primeiro na ordem de leitura, / tenha precedência superior à +, também 6 / 2 é a subexpressão que avalia primeiro. Avançando passo a passo — exatamente como farias no papel:

3 + 6 / 2
      ↓   (evaluate 6 / 2 → 3)
3 +   3
  ↓   (evaluate 3 + 3 → 6)
  6
// 3 + 6 / 2
// Step 1: 6 / 2 has higher precedence → interim value 3
// Step 2: 3 + 3 → final result 6
int result = 3 + 6 / 2;
Console.WriteLine(result); // => 6

O valor 3 intermédio produzido por 6 / 2 torna-se o operando direito de +, e o resultado final é 6. Cada subexpressão produz um valor intermediário; esses valores intermédios alimentam a próxima subexpressão, até que resta apenas um valor.

A associatividade é um conceito relacionado: quando dois operadores têm a mesma precedência, a associatividade decide qual deles começa primeiro. A maioria dos operadores C# são associativos à esquerda, ou seja, agrupam-se da esquerda para a direita. Portanto, a - b - c é o mesmo que (a - b) - c, não a - (b - c).

Avaliação de curtos-circuitos na prática

A avaliação de curtos-circuitos é particularmente útil para verificações nulas:

string? text = null;

// Safe: second condition runs only when text is not null
bool hasContent = text != null && text.Length > 0;
Console.WriteLine(hasContent); // => False  (short-circuits after null check; no NullReferenceException)

text = "hello";
hasContent = text != null && text.Length > 0;
Console.WriteLine(hasContent); // => True

// || short-circuits on true: right side is never evaluated when left side is true
string word = "hello";
bool anyMatch = word.StartsWith("h") || word.StartsWith("x");
Console.WriteLine(anyMatch); // => True  (right side never evaluated)

// ?. short-circuits on null: returns null without accessing .Length
string? maybeNull = null;
int? length = maybeNull?.Length;   // length is null; no NullReferenceException
Console.WriteLine(length.HasValue); // => False

text != null && text.Length > 0 é seguro porque a segunda condição só é avaliada quando text não é null. De forma semelhante, ?. interrompe a avaliação quando encontra uma referência null, o que evita NullReferenceException sem uma verificação explícita de if.

Para uma visão mais ampla dos operadores null-safe, veja operadores nulos em C#.

Consulte também