Espressioni Condizionali in C#

Le espressioni condizionali in C# offrono modi concisi ed eleganti per eseguire logica decisionale all’interno del codice. Dalle classiche espressioni ternarie alle moderne espressioni switch e pattern matching introdotte nelle versioni recenti, C# fornisce una ricca gamma di strumenti per gestire la logica condizionale in modo espressivo e performante.
Operatore Condizionale Ternario (?:)
Sintassi Base
L’operatore ternario è la forma più semplice di espressione condizionale, che consente di scrivere un’istruzione if-else in una singola espressione.
// Sintassi: condizione ? valore_se_vero : valore_se_falso
int età = 20;
string categoria = età >= 18 ? "Adulto" : "Minorenne";
// Equivalente a:
string categoria2;
if (età >= 18)
categoria2 = "Adulto";
else
categoria2 = "Minorenne";
Esempi Pratici
// Determinazione del prezzo con sconto
double prezzo = 100.0;
bool clientePremium = true;
double prezzoFinale = clientePremium ? prezzo * 0.8 : prezzo;
// Validazione input
string input = "";
string messaggio = string.IsNullOrEmpty(input) ? "Input richiesto" : "Input valido";
// Calcolo del massimo
int a = 15, b = 23;
int massimo = a > b ? a : b;
// Assegnazione condizionale null
string nome = null;
string nomeVisualizzato = nome ?? "Nome non disponibile";
// Equivalente con operatore ternario:
string nomeVisualizzato2 = nome != null ? nome : "Nome non disponibile";
Operatori Ternari Annidati
Concatenazione di Condizioni
int punteggio = 85;
string voto = punteggio >= 90 ? "A" :
punteggio >= 80 ? "B" :
punteggio >= 70 ? "C" :
punteggio >= 60 ? "D" : "F";
// Esempio più complesso con multiple condizioni
int temperatura = 25;
bool piove = false;
string consiglioAbbigliamento = temperatura > 30 ? "Vestiti leggeri" :
temperatura > 20 ? (piove ? "Giacca impermeabile" : "Maglietta") :
temperatura > 10 ? "Maglione" :
"Cappotto pesante";
Best Practice per Operatori Annidati
// ❌ Difficile da leggere
string risultato = condizione1 ? valore1 : condizione2 ? valore2 : condizione3 ? valore3 : valore4;
// ✅ Più leggibile con indentazione
string risultato = condizione1 ? valore1 :
condizione2 ? valore2 :
condizione3 ? valore3 :
valore4;
// ✅ Meglio ancora: switch expression per casi complessi
string risultato = punteggio switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
Espressioni Switch (C# 8.0+)
Sintassi Base
// Switch expression moderna
string giorno = "lunedì";
string tipoGiorno = giorno switch
{
"lunedì" or "martedì" or "mercoledì" or "giovedì" or "venerdì" => "Lavorativo",
"sabato" or "domenica" => "Weekend",
_ => "Giorno non riconosciuto"
};
// Equivalente switch statement tradizionale
string tipoGiorno2;
switch (giorno)
{
case "lunedì":
case "martedì":
case "mercoledì":
case "giovedì":
case "venerdì":
tipoGiorno2 = "Lavorativo";
break;
case "sabato":
case "domenica":
tipoGiorno2 = "Weekend";
break;
default:
tipoGiorno2 = "Giorno non riconosciuto";
break;
}
Switch con Pattern Matching
// Pattern matching con tipi
object valore = 42;
string descrizione = valore switch
{
int i when i > 0 => $"Numero positivo: {i}",
int i when i < 0 => $"Numero negativo: {i}",
int i when i == 0 => "Zero",
string s when !string.IsNullOrEmpty(s) => $"Stringa: {s}",
string s => "Stringa vuota",
bool b => $"Booleano: {b}",
null => "Valore null",
_ => "Tipo non gestito"
};
// Pattern con tuple
(int x, int y) punto = (3, 4);
string quadrante = punto switch
{
(0, 0) => "Origine",
(> 0, > 0) => "Primo quadrante",
(< 0, > 0) => "Secondo quadrante",
(< 0, < 0) => "Terzo quadrante",
(> 0, < 0) => "Quarto quadrante",
(0, _) => "Asse X",
(_, 0) => "Asse Y"
};
Espressioni Condizionali con Null
Operatore Null-Coalescing (??)
string nome = null;
string nomeDisplay = nome ?? "Utente anonimo";
// Concatenazione di operatori null-coalescing
string config = Environment.GetEnvironmentVariable("CONFIG") ??
File.ReadAllText("config.txt") ??
"configurazione predefinita";
// Con tipi di riferimento nullable (C# 8.0+)
string? input = GetUserInput();
string messaggio = input ?? throw new ArgumentException("Input richiesto");
Operatore Null-Conditional (?.)
// Accesso sicuro a membri
Person? persona = GetPerson();
string? nome = persona?.Nome;
int? lunghezzaNome = persona?.Nome?.Length;
// Con indexer
string[]? array = GetArray();
string? primoElemento = array?[0];
// Chiamata di metodi condizionale
List<string>? lista = GetList();
lista?.Add("nuovo elemento"); // Add viene chiamato solo se lista non è null
// Combinazione con null-coalescing
string nomeCompleto = persona?.Nome ?? "Nome sconosciuto";
int conteggio = lista?.Count ?? 0;
Pattern Matching Avanzato
Property Pattern
public record Persona(string Nome, int Età, string Città);
Persona persona = new("Mario", 30, "Milano");
string categoria = persona switch
{
{ Età: < 18 } => "Minorenne",
{ Età: >= 18, Età: < 65 } => "Adulto",
{ Età: >= 65 } => "Anziano",
_ => "Età non valida"
};
// Pattern più complessi
string descrizione = persona switch
{
{ Nome: "Mario", Città: "Milano" } => "Mario di Milano",
{ Età: > 25, Città: "Roma" } => "Adulto romano",
{ Nome: var n, Età: var e } when e > 30 => $"{n} over 30",
_ => "Persona generica"
};
Relational Pattern (C# 9.0+)
int temperatura = 25;
string descrizione = temperatura switch
{
< 0 => "Sotto zero",
>= 0 and < 10 => "Freddo",
>= 10 and < 20 => "Fresco",
>= 20 and < 30 => "Mite",
>= 30 => "Caldo"
};
// Logical pattern
bool IsWeekend(DateTime data) => data.DayOfWeek switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => true,
_ => false
};
// Negation pattern
bool IsNotWeekend(DateTime data) => data.DayOfWeek switch
{
not (DayOfWeek.Saturday or DayOfWeek.Sunday) => true,
_ => false
};
Espressioni Condizionali Complesse
Combinazione di Operatori
public class CalcolatoreSconto
{
public decimal CalcolaSconto(decimal importo, bool clientePremium, int ordiniPrecedenti)
{
// Combinazione di operatori condizionali
decimal scontoBase = importo > 1000 ? 0.1m :
importo > 500 ? 0.05m : 0m;
decimal scontoFedeltà = ordiniPrecedenti switch
{
> 10 => 0.15m,
> 5 => 0.1m,
> 0 => 0.05m,
_ => 0m
};
decimal scontoPremium = clientePremium ? 0.1m : 0m;
// Sconto massimo applicabile
return Math.Min(scontoBase + scontoFedeltà + scontoPremium, 0.3m);
}
}
// Validazione complessa con pattern matching
public bool ValidaUtente(object utente) => utente switch
{
null => false,
{ } when utente.GetType() != typeof(User) => false,
User { Nome: null or "" } => false,
User { Email: var email } when !email.Contains("@") => false,
User { Età: < 0 or > 150 } => false,
User => true
};
Guard Clauses con Espressioni
public string ProcessaInput(string? input, bool isAdmin)
{
// Guard clauses usando espressioni condizionali
_ = input ?? throw new ArgumentNullException(nameof(input));
_ = input.Length > 0 ? input : throw new ArgumentException("Input vuoto");
return isAdmin ?
input.ToUpper() :
input.Length > 10 ?
input.Substring(0, 10) + "..." :
input;
}
// Versione con switch expression
public string ProcessaInput2(string? input, UserRole role) =>
(input, role) switch
{
(null, _) => throw new ArgumentNullException(nameof(input)),
("", _) => throw new ArgumentException("Input vuoto"),
(_, UserRole.Admin) => input.ToUpper(),
(var s, _) when s.Length > 10 => s.Substring(0, 10) + "...",
(var s, _) => s
};
Applicazioni Pratiche
Sistema di Configurazione
public class ConfigManager
{
public T GetConfigValue<T>(string key, T defaultValue)
{
string? value = Environment.GetEnvironmentVariable(key);
return value switch
{
null => defaultValue,
_ when typeof(T) == typeof(int) => (T)(object)int.Parse(value),
_ when typeof(T) == typeof(bool) => (T)(object)bool.Parse(value),
_ when typeof(T) == typeof(string) => (T)(object)value,
_ => defaultValue
};
}
public LogLevel GetLogLevel() =>
Environment.GetEnvironmentVariable("LOG_LEVEL")?.ToLower() switch
{
"debug" => LogLevel.Debug,
"info" => LogLevel.Information,
"warn" or "warning" => LogLevel.Warning,
"error" => LogLevel.Error,
"critical" or "fatal" => LogLevel.Critical,
_ => LogLevel.Information
};
}
Validatore di Business Rules
public class BusinessRuleValidator
{
public ValidationResult ValidateOrder(Order order) =>
order switch
{
null => new ValidationResult(false, "Ordine non può essere null"),
{ Items.Count: 0 } => new ValidationResult(false, "Ordine deve contenere almeno un articolo"),
{ Customer: null } => new ValidationResult(false, "Cliente richiesto"),
{ Total: <= 0 } => new ValidationResult(false, "Totale deve essere positivo"),
{ Customer.Age: < 18, Items: var items } when items.Any(i => i.Category == "Alcohol") =>
new ValidationResult(false, "Minorenne non può acquistare alcolici"),
{ ShippingAddress: null, DeliveryMethod: DeliveryMethod.Shipping } =>
new ValidationResult(false, "Indirizzo di spedizione richiesto"),
_ => new ValidationResult(true, "Ordine valido")
};
public decimal CalculateShipping(Order order) =>
(order.Weight, order.Customer.IsPremium, order.DeliveryMethod) switch
{
(_, true, _) => 0m, // Spedizione gratuita per clienti premium
(< 1, false, DeliveryMethod.Standard) => 5m,
(>= 1, false, DeliveryMethod.Standard) => 10m,
(_, false, DeliveryMethod.Express) => 25m,
_ => 15m
};
}
Performance e Best Practices
Quando Usare Ogni Tipo di Espressione
// ✅ Operatore ternario per logica semplice
string status = isActive ? "Attivo" : "Inattivo";
// ✅ Switch expression per multiple opzioni
string dayType = dayOfWeek switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
_ => "Weekday"
};
// ❌ Evitare ternario annidato complesso
// string result = cond1 ? val1 : cond2 ? val2 : cond3 ? val3 : val4;
// ✅ Preferire switch expression per logica complessa
string result = input switch
{
var x when x > 100 => "Alto",
var x when x > 50 => "Medio",
_ => "Basso"
};
Considerazioni su Null Safety
Con l’introduzione dei nullable reference types in C# 8.0, le espressioni condizionali diventano ancora più potenti per gestire la null safety:
// Gestione sicura di valori nullable
public string GetDisplayName(User? user) =>
user?.Name ?? "Guest User";
// Pattern matching con null check
public bool IsValidEmail(string? email) => email switch
{
null or "" => false,
var e when e.Contains("@") && e.Contains(".") => true,
_ => false
};
Migrazione da Versioni Precedenti
Da If-Else a Switch Expression
// Vecchio stile
string GetGradeOld(int score)
{
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else if (score >= 60) return "D";
else return "F";
}
// Nuovo stile con switch expression
string GetGradeNew(int score) => score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
Considerazioni sulle Performance
Le espressioni condizionali moderne offrono non solo una sintassi più pulita, ma anche potenziali miglioramenti delle performance:
- Switch expressions sono ottimizzate dal compilatore e possono essere più efficienti di catene if-else
- Pattern matching elimina la necessità di cast espliciti e controlli di tipo ridondanti
- Operatori null-conditional prevengono eccezioni NullReferenceException senza overhead significativo
Conclusione
Le espressioni condizionali in C# hanno subito un’evoluzione significativa, passando dal semplice operatore ternario alle potenti switch expressions con pattern matching. Questi strumenti moderni consentono di scrivere codice più espressivo, sicuro e performante. La chiave è scegliere l’approccio giusto per ogni situazione: operatori ternari per logica semplice, switch expressions per scenari complessi, e pattern matching per lavorare con tipi e strutture dati articolate. L’adozione di queste tecniche moderne migliora significativamente la leggibilità e la manutenibilità del codice C#.