Un operatore prende uno, due o tre operandi e produce un valore: a + b, !done, x ?? fallback. Gli operatori di C# sono simili a quelli di C, Java e JavaScript, ma alcuni si comportano in modi che sorprendono chi inizia, soprattutto la divisione intera e il segno di %.
Operatori aritmetici
+, -, *, / e % funzionano su tutti i tipi numerici. Il tipo del risultato dipende dagli operandi: due valori int producono un int, un int e un double producono un double.
Output:
22
12
85
3
2
3.4
3.4
3
3.4
La divisione intera tronca verso lo zero. 17 / 5 vale 3 e -17 / 5 vale -3. È la causa di un bug molto comune: int percent = done / total * 100; vale 0 ogni volta che done < total, perché done / total è già 0. Moltiplica prima (done * 100 / total) oppure dividi come double.
Dividere un intero o un decimal per zero lancia DivideByZeroException. Dividere un double per zero non lancia nulla; produce infinito, oppure NaN per 0.0 / 0.0:
Output:
True
True
False
DivideByZeroException
L'operatore modulo e i numeri negativi
% restituisce il resto della divisione, e il suo risultato prende il segno dell'operando di sinistra. Questo è diverso da Python e dal "mod" matematico, che sono sempre non negativi con un divisore positivo:
Output:
1
-1
1
2
even
62 min 5 s
Thu
1.5
Usa la funzione di supporto Mod ogni volta che il lato sinistro può essere negativo e il risultato è un indice: days[(today + offset) % 7] lancerebbe una IndexOutOfRangeException con una somma negativa. Anche il test per i numeri dispari ha la stessa trappola: n % 2 == 1 è false per i numeri dispari negativi; verifica n % 2 != 0.
Assegnazione, assegnazione composta e incremento
= assegna. Le forme composte +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= combinano un'operazione con l'assegnazione. ++ e -- aggiungono o sottraggono 1, ed esistono in forma prefissa e postfissa, che differiscono nel valore che restituiscono:
Output:
14
start, step
a=5 b=7 i=7
4
Il caso del byte mostra una particolarità: level = level + 10; non compila, perché byte + int è un int, mentre level += 10; sì, perché l'assegnazione composta include un cast implicito verso il tipo della variabile. Quel cast ricomincia da capo in caso di overflow (250 + 10 diventa 4).
Operatori di confronto e logici
Gli operatori di confronto (==, !=, <, >, <=, >=) restituiscono un bool. Per le stringhe, == confronta il testo, non i riferimenti. Gli operatori logici sono ! (not), && (and), || (or) e ^ (or esclusivo).
Output:
True
no active user
with ||:
evaluated A
with |:
evaluated A
evaluated B
True
&& e || cortocircuitano: l'operando di destra viene eseguito solo quando può cambiare il risultato. È questo che rende sicuro user != null && user.IsActive. I singoli & e | sui booleani valutano sempre entrambi i lati, cosa che raramente vuoi in una condizione.
Operatori bitwise e di shift
Sui tipi interi, &, |, ^ e ~ lavorano sui singoli bit, e << e >> li spostano. Si usano per flag, maschere, hashing e protocolli di basso livello:
Output:
3
True
False
101
1024
125
5
-1
Per insiemi di flag con un nome, un enum con l'attributo [Flags] è più chiaro delle costanti intere, e HasFlag si legge meglio di una maschera. C# 11 ha aggiunto >>>, uno shift a destra senza segno che riempie con zeri anche per i numeri negativi.
Operatori per null
Esistono tre operatori per lavorare con valori che possono essere null:
Output:
Guest
0
5
a ?? b restituisce a a meno che non sia null, altrimenti b. a?.Member restituisce null invece di lanciare un'eccezione quando a è null. C# 8 ha aggiunto a ??= b, che assegna b ad a solo quando a è null. Sono trattati a fondo nei tipi nullable.
Precedenza degli operatori
Quando un'espressione mescola operatori, le righe più in alto legano più strettamente. Gli operatori della stessa riga si raggruppano da sinistra a destra, tranne l'assegnazione, ?? e ?:, che si raggruppano da destra a sinistra. (Gli operandi vengono sempre valutati da sinistra a destra.)
| Categoria | Operatori |
|---|---|
| Primari | x.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof |
| Unari | +x, -x, !x, ~x, ++x, --x, (T)x, await |
| Moltiplicativi | *, /, % |
| Additivi | +, - |
| Shift | <<, >>, >>> |
| Relazionali e test di tipo | <, >, <=, >=, is, as |
| Uguaglianza | ==, != |
| AND bit a bit | & |
| XOR bit a bit | ^ |
| OR bit a bit | | |
| AND condizionale | && |
| OR condizionale | || |
| Null coalescing | ?? |
| Condizionale | c ? a : b |
| Assegnazione e lambda | =, +=, -=, ??=, => e le altre forme composte |
Due conseguenze da conoscere: a + b * c moltiplica per prima cosa, e x & mask == 0 confronta per primo (== lega più strettamente di &), quindi significa x & (mask == 0) e non compila con gli interi. Scrivi (x & mask) == 0. Le parentesi non costano nulla e rendono evidente l'intenzione.
Overloading degli operatori
I tuoi tipi possono definire cosa significano gli operatori. È così che decimal, DateTime e TimeSpan supportano + e <:
Output:
33.49
True
Gli operatori devono essere public static, e alcuni vanno obbligatoriamente in coppia: definire < richiede >, e == richiede !=. Con == definito, il compilatore avvisa anche finché non ridefinisci Equals e GetHashCode in modo coerente. Fai l'overloading degli operatori solo dove il significato è ovvio, come con numeri, vettori e importi di denaro.
Domande frequenti
Cosa fa % in C#?
% è l'operatore di resto: 17 % 5 vale 2. Il risultato ha il segno dell'operando di sinistra, quindi -7 % 3 vale -1, non 2 come in Python. Per un risultato sempre non negativo, come serve quando fai ricominciare un indice da capo, scrivi ((a % n) + n) % n. Funziona anche su double e decimal: 7.5 % 2 vale 1.5.
Perché 7 / 2 fa 3 in C#?
Quando entrambi gli operandi sono interi, / esegue la divisione intera e scarta la parte frazionaria, troncando verso lo zero. Rendi uno degli operandi un valore in virgola mobile o decimal per ottenere un risultato con la parte decimale: 7 / 2.0 vale 3.5, (double)a / b converte prima di dividere, e 7m / 2 vale 3.5 come decimal.
Che differenza c'è tra && e & in C#?
&& è l'AND condizionale: se il lato sinistro è false, il lato destro non viene valutato. & sui booleani valuta sempre entrambi i lati, e sugli interi è l'AND bit a bit. Usa && e || per le condizioni, soprattutto per controlli come user != null && user.IsActive, che con & lancerebbe una NullReferenceException.
Che differenza c'è tra i++ e ++i in C#?
Entrambi aggiungono 1 a i. L'espressione i++ vale il vecchio valore, ++i il nuovo. Su una riga da sola (i++;) non c'è differenza. Conta solo quando il risultato viene usato, come in int a = i++;, ed è per questo che molte guide di stile tengono gli incrementi su una riga a parte.
Qual è la precedenza degli operatori in C#?
Dalla più alta alla più bassa: primari (x.y, f(x), a[i], x++), unari (!, -, ++x, cast), moltiplicativi (* / %), additivi (+ -), shift, relazionali (< > <= >= is as), uguaglianza (== !=), &, ^, |, &&, ||, ??, il condizionale ?: e infine l'assegnazione. Nel dubbio, aggiungi le parentesi.