Expressões em C#

Dica

Este artigo faz parte da seção Conceitos Básicos para desenvolvedores que já conhecem pelo menos uma linguagem de programação e estão aprendendo C#. Se você é novo em programação, comece com os tutoriais Comece agora.

Vindo de outro idioma? As expressões em C# funcionam tanto quanto em Java, C++e JavaScript. Uma diferença que vale a pena observar: operadores de atribuição composta como += e o operador ++ de incremento são expressões em C#, para que possam aparecer em expressões maiores.

Uma expressão é uma parte do código que o compilador avalia para produzir um valor – um número, uma cadeia de caracteres, uma referência ou um bool. Por exemplo, 3 + 4 * 2 é uma expressão que é avaliada como o inteiro 11e 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). Você cria expressões mais complexas combinando as mais simples com operadores.

Expressões e declarações

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

Você pode pensar em uma expressão como uma frase e uma instrução C# como uma frase completa. Essas definições de trabalho ajudam você a entender a terminologia formal e as explicações que você encontrará à medida que aprender mais sobre C#, embora a codificação comum raramente exija que você se lembre da distinção conscientemente.

Combinando expressões

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

Você não precisa memorizar a hierarquia de precedência completa. A próxima seção resume os grupos cotidianos e os parênteses sempre permitem que você torne a ordem explícita quando não tiver certeza.

Precedência do operador

A precedência do operador determina como uma expressão combinada se agrupa em subexpressões. Esse conceito é semelhante às regras de ordem de operações da classe matemática. Um operador com precedência mais alta associa-se mais fortemente aos seus operandos. A expressão é estruturada como se esses operandos estivessem agrupados entre parênteses. Em 3 + 4 * 2, * tem precedência sobre +, portanto, a expressão é agrupada como 3 + (4 * 2), não como (3 + 4) * 2.

O C# tem mais grupos de precedência do que os quatro resumidos aqui. 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 abrangem o que você encontra com mais frequência no código cotidiano:

  1. Primário, unário e intervalo – são três grupos de precedência distintos, todos os quais se associam mais fortemente do que a aritmética. Este resumo os combina em uma etapa porque, para fins práticos, todos se aplicam antes da aritmética.
    • Operadores primários – acesso de membro (x.y), chamadas de método (f()), indexação (a[i]) e acesso condicional nulo (?., ?[]) – associam mais fortemente de todos.
    • Operadores unários atuam em um único operando: negação (-x), NOT lógico (!flag), incremento de prefixo (++i) e incremento de postfixo (i++).
    • O operador de intervalo (..) cria intervalos de índice para expressões de fatia, como array[1..4].
  2. Aritmética*, %, / se associam mais fortemente do que + e -. Multiplicação e divisão ocorrem antes da adição e da subtração.
  3. Comparação<, >, <=, >=, ==, != se vinculam menos fortemente do que a aritmética, portanto a aritmética é concluída antes da comparação.
  4. Lógico&& e || associe menos fortemente os operadores comuns, portanto, as comparações são concluídas antes da combinação lógica.

Essa precedência significa que a expressão score + bonus > threshold && attempts < maxAttempts é avaliada exatamente como você a leria: adicionar score ebonus, comparar a soma comthreshold, comparar maxAttemptsattempts e 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 obter a hierarquia de precedência completa que abrange todos os operadores, consulte operadores e expressões em C#.

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

Os parênteses substituem a precedência e documentam sua intenção ao mesmo tempo. Quando a ordem não for óbvia dos 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, adicione parênteses para deixar claro qual condição é combinada primeiro. Um leitor que vê (isAdmin && isOwner) || isSuperUser sabe a intenção imediatamente.

Como as expressões são avaliadas

Entender como o C# avalia expressões combinadas é mais fácil com uma analogia. Pense em trabalhar em um problema matemático complexo com lápis e papel: você identifica a subexpressão mais interna ou de precedência mais alta, calcula seu valor provisório, anota o resultado e, em seguida, repete com a próxima subexpressão, continuando até chegar à resposta final.

O 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 que o operador seja aplicado. C# avalia o operando esquerdo primeiro, depois a direita e, em seguida, executa a operação.

Regra 2: Alguns operadores fazem curto-circuito. Determinados operadores param de avaliar assim que o resultado é determinado, ignorando todos os operandos restantes:

  • && (AND condicional): retorna false assim que o lado esquerdo é false. O lado direito nunca é avaliado.
  • || (OR condicional): retorna true assim que o lado esquerdo é true. O lado direito nunca é avaliado.
  • ?: (condicional/ternário): avalia apenas o branch que corresponde à condição — o outro branch nunca é avaliado.
  • ?. (acesso de membro condicional nulo) e ?[] (acesso de elemento condicional nulo): pare e retorne null imediatamente quando o lado esquerdo estiver null, ignorando o acesso ou o índice do membro.
  • ??= (atribuição de coalescência nula): atribui o lado direito somente 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, / tem precedência maior do que +, assim 6 / 2 como a subexpressão que é avaliada primeiro. Trabalhar com ele passo a passo , exatamente como você faria 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 provisório produzido por 6 / 2 torna-se o operando certo de +, e o resultado final é 6. Cada subexpressão produz um valor intermediário; esses valores intermediários alimentam a próxima subexpressão, até que reste apenas um valor.

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

Avaliação de curto-circuito na prática

A avaliação de curto-circuito é 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 é executada somente quando text não é null. Da mesma forma, ?. interrompe a avaliação quando encontra uma referência null, o que evita um(a) NullReferenceException sem uma verificação explícita de if.

Para obter uma visão mais ampla dos operadores nulos seguros, consulte operadores nulos em C#.

Consulte também