Espressioni Condizionali in C#

Edoardo Midali
Edoardo Midali

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#.