Espressioni C#

Tip

Questo articolo fa parte della sezione Nozioni fondamentali per gli sviluppatori che già conoscono almeno un linguaggio di programmazione e stanno imparando C#. Se non si ha familiarità con la programmazione, iniziare prima con le esercitazioni introduttive .

Venire da un'altra lingua? Le espressioni in C# funzionano come funzionano in Java, C++ e JavaScript. Una differenza vale la pena notare: gli operatori di assegnazione composta come += e l'operatore ++ di incremento sono espressioni in C#, in modo che possano essere visualizzate in espressioni più grandi.

Un espressione è un frammento di codice che il compilatore valuta per produrre un valore — un numero, una stringa, un riferimento o un bool. Ad esempio, 3 + 4 * 2 è un'espressione che restituisce l'intero 11ed total > 10 è un'espressione che restituisce true o false.

Le espressioni più semplici sono valori letterali (ad esempio 42 o "hello") e nomi di variabili (ad esempio total). È possibile creare espressioni più complesse combinando quelle più semplici con gli operatori.

Espressioni e istruzioni

Un'espressione produce un valore. Un'istruzione è un'istruzione completa eseguita dal programma. Molte istruzioni contengono espressioni. Ad esempio, int total = 3 + 4 * 2; è un'istruzione di dichiarazione di variabile. Il compilatore valuta l'espressione 3 + 4 * 2dell'inizializzatore , che produce 11e assegna tale valore alla nuova variabile total.

È possibile considerare un'espressione come una frase e un'istruzione C# come una frase completa. Queste definizioni di lavoro consentono di comprendere la terminologia formale e le spiegazioni che si incontreranno man mano che si apprenderanno altre informazioni su C#, anche se la codifica comune richiede raramente di ricordare la distinzione in modo consapevole.

Combinazione di espressioni

Le espressioni possono essere combinate. Considera 3 + 4 * 2. Questa singola espressione contiene effettivamente due espressioni più piccole: l'espressione 4 * 2di moltiplicazione e l'espressione 3 + <result>di addizione. Quando le espressioni vengono combinate, C# richiede una regola per decidere quale valutare per prima. Tale regola è la precedenza dell'operatore.

Non è necessario memorizzare la gerarchia di precedenza completa. La sezione successiva riepiloga i gruppi quotidiani e le parentesi consentono sempre di rendere l'ordine esplicito quando non si è certi.

Precedenza dell'operatore

La precedenza dell'operatore determina il modo in cui un'espressione combinata si raggruppa in sottoespressione. Questo concetto è simile alle regole di ordine delle operazioni della classe matematica. Un operatore con precedenza superiore si lega più strettamente ai suoi operandi. L'espressione è strutturata come se tali operandi siano racchiusi tra parentesi. In 3 + 4 * 2, * ha una precedenza maggiore di +, quindi i gruppi di espressioni come 3 + (4 * 2), non (3 + 4) * 2.

C# ha più gruppi di precedenza rispetto ai quattro riepilogati qui. Questi gruppi aggiuntivi applicano regole familiari, ad esempio la moltiplicazione prima dell'aggiunta, e coprono il set completo di operatori. I gruppi seguenti illustrano ciò che si incontra più spesso nel codice quotidiano:

  1. Primario, unario e intervallo : si tratta di tre gruppi di precedenza distinti, tutti più strettamente associati all'aritmetica. Questo riepilogo li combina in un unico passaggio perché, per scopi pratici, si applicano tutti prima dell'aritmetica.
    • Operatori primari : accesso ai membri (x.y), chiamate al metodo (f()), indicizzazione (a[i]) e accesso condizionale null (?., ?[]) - associano più strettamente tutti.
    • Gli operatori unari agiscono su un singolo operando: negazione (-x), NOT logico (!flag), incremento del prefisso (++i) e incremento del prefisso (i++).
    • L'operatore range (..) compila gli intervalli di indici per le espressioni di sezione, ad esempio array[1..4].
  2. Aritmetica : *, /, associare % più strettamente di + e -. La moltiplicazione e la divisione si verificano prima dell'addizione e della sottrazione.
  3. Operatori di confronto<, <=, >, >=, ==, != si legano meno strettamente degli operatori aritmetici, quindi le operazioni aritmetiche vengono eseguite prima del confronto.
  4. Logico : && e || associa meno strettamente gli operatori comuni, quindi i confronti vengono completati prima della combinazione logica.

Questa precedenza indica che l'espressione score + bonus > threshold && attempts < maxAttempts valuta esattamente come si legge: aggiungere score e bonus, confrontare la somma con threshold, confrontare attempts con maxAttempts, quindi combinare i due bool risultati con &&. L'aggiunta di parentesi per rendere esplicito ogni raggruppamento mostra la stessa struttura: ((score + bonus) > threshold) && (attempts < maxAttempts).

Per la gerarchia di precedenza completa che copre ogni operatore, vedere Operatori ed espressioni C#.

Usare le parentesi per chiarire la finalità

Le parentesi sostituiscono la precedenza e documentano contemporaneamente la finalità. Quando l'ordine non è ovvio dai gruppi precedenti, aggiungere parentesi:

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)

Le ultime due righe mostrano che le parentesi possono modificare il risultato, non solo lo stile. Quando && e || appaiono insieme, aggiungere parentesi per specificare quale condizione combina per prima. Un lettore che vede (isAdmin && isOwner) || isSuperUser capisce immediatamente l'intento.

Modalità di valutazione delle espressioni

Comprendere in che modo C# valuta le espressioni combinate è più semplice con un'analogia. Si pensi a un problema matematico complesso con matita e carta: si identifica la sottoespressione più interna o più alta, calcola il valore provvisorio, si scrive il risultato, quindi si ripete con la sottoespressione successiva, continuando fino a raggiungere la risposta finale.

C# segue lo stesso processo, guidato da due regole:

Regola 1: Gli operandi vengono valutati da sinistra a destra. Per qualsiasi espressione binaria, entrambi gli operandi devono essere valutati completamente prima dell'applicazione dell'operatore. C# valuta prima l'operando sinistro, poi quello destro, quindi esegue l'operazione.

Regola 2: Alcuni operatori sono in corto circuito. Alcuni operatori interrompono la valutazione non appena il risultato è determinato, saltando gli eventuali operandi rimanenti:

  • && (AND condizionale): restituisce false non appena il lato sinistro è false. Il lato destro non viene mai valutato.
  • || (OR condizionale): restituisce true non appena il lato sinistro è true. Il lato destro non viene mai valutato.
  • ?: (condizionale/ternario): valuta solo il ramo che corrisponde alla condizione . L'altro ramo non viene mai valutato.
  • ?. (accesso condizionale ai membri con null) e ?[] (accesso condizionale agli elementi con null): si interrompono e restituiscono immediatamente null quando la parte sinistra è null, saltando l'accesso al membro o all'indice.
  • ??= (assegnazione con coalescenza null): assegna il valore di destra solo se il lato sinistro è null.

Valutazione carta e matita

Si consideri l'espressione 3 + 6 / 2. Anche se l'aggiunta viene visualizzata per prima nell'ordine di lettura, / ha una precedenza superiore a +, quindi 6 / 2 è la sottoespressione che valuta per prima. Lavorare passo per passo - esattamente come si farebbe su carta:

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

Il valore 3 provvisorio prodotto da 6 / 2 diventa l'operando destro di +e il risultato finale è 6. Ogni sottoespressione produce un valore provvisorio; tali valori provvisori alimentano la sottoespressione successiva, fino a quando non rimane un solo valore.

L'associatività è un concetto correlato: quando due operatori hanno la stessa precedenza, l'associatività decide quale va prima. La maggior parte degli operatori C# è associativa a sinistra, ovvero raggruppano da sinistra a destra. Quindi a - b - c è uguale (a - b) - ca , non a - (b - c).

Valutazione a corto circuito in pratica

La valutazione a corto circuito è particolarmente utile per i controlli Null:

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 è sicuro perché la seconda condizione viene eseguita solo quando text non è null. Analogamente, ?. interrompe la valutazione quando incontra un riferimento null, evitando un NullReferenceException senza un controllo esplicito di if.

Per un'analisi più ampia degli operatori null-safe, vedere Operatori Null C#.

Vedere anche