Operatori e strutture di controllo in C#

Operatori e strutture di controllo in C#

Un programma diventa davvero utile quando è in grado di prendere decisioni e di ripetere operazioni. Gli operatori permettono di combinare e confrontare i valori, mentre le strutture di controllo determinano il flusso di esecuzione, ovvero l'ordine in cui le istruzioni vengono eseguite. In questo articolo esaminiamo gli operatori principali di C# e le costruzioni che governano condizioni e cicli, elementi che ritroveremo in ogni programma.

Operatori aritmetici e di assegnazione

Gli operatori aritmetici eseguono i calcoli fondamentali. Oltre alle quattro operazioni classiche, C# fornisce l'operatore modulo, che restituisce il resto di una divisione, molto utile per verificare la parità o per lavorare con cicli periodici.

int a = 10;
int b = 3;

int sum = a + b;        // 13
int difference = a - b; // 7
int product = a * b;    // 30
int quotient = a / b;   // 3 (divisione intera)
int remainder = a % b;  // 1 (resto della divisione)

Gli operatori di assegnazione composta abbreviano le operazioni che modificano una variabile in base al suo valore corrente, mentre gli operatori di incremento e decremento aumentano o diminuiscono di uno.

int counter = 5;
counter += 3; // equivale a counter = counter + 3, ora vale 8
counter -= 2; // ora vale 6
counter++;    // incremento, ora vale 7
counter--;    // decremento, ora vale 6

Operatori relazionali e logici

Gli operatori relazionali confrontano due valori e producono un risultato booleano. Gli operatori logici combinano più condizioni booleane, permettendo di costruire espressioni complesse.

int age = 25;

bool isAdult = age >= 18;        // true
bool isTeenager = age >= 13 && age <= 19; // false: entrambe devono essere vere
bool isSpecialAge = age == 18 || age == 21; // false: basta una vera

Gli operatori logici && e || sono a valutazione "cortocircuitata": se il risultato è già determinato dal primo operando, il secondo non viene valutato. Questo comportamento è spesso sfruttato per evitare valutazioni non necessarie o potenzialmente problematiche.

Operatori per i valori nulli

C# offre operatori pensati specificamente per gestire i valori nulli in modo conciso. L'operatore null-coalescing ?? fornisce un valore alternativo quando l'espressione a sinistra è nulla, mentre l'operatore di accesso condizionale ?. evita di sollevare un'eccezione quando si accede a un membro di un riferimento potenzialmente nullo.

string? input = null;

// Se input e null, si usa il valore predefinito
string result = input ?? "valore predefinito";

// L'accesso condizionale restituisce null invece di sollevare un'eccezione
int? length = input?.Length;

L'istruzione if

L'istruzione if è la struttura decisionale più elementare: esegue un blocco di codice solo se una condizione è vera. Può essere estesa con else per gestire il caso contrario e con else if per valutare condizioni in sequenza.

int score = 75;

if (score >= 90)
{
    Console.WriteLine("Ottimo");
}
else if (score >= 60)
{
    Console.WriteLine("Sufficiente");
}
else
{
    Console.WriteLine("Insufficiente");
}

Per condizioni semplici che assegnano uno di due valori esiste l'operatore ternario, una forma compatta di scelta binaria che si legge come "condizione, valore se vera, valore se falsa":

string label = score >= 60 ? "Promosso" : "Bocciato";

Lo switch

Quando si deve confrontare una stessa variabile con molti valori possibili, la catena di if diventa verbosa. L'istruzione switch offre un'alternativa più leggibile. Ogni ramo case gestisce un valore, e il ramo default intercetta tutti i casi non previsti.

int day = 3;

switch (day)
{
    case 1:
        Console.WriteLine("Lunedi");
        break;
    case 6:
    case 7:
        Console.WriteLine("Weekend");
        break;
    default:
        Console.WriteLine("Giorno feriale");
        break;
}

Le versioni moderne di C# introducono la switch expression, una forma più concisa che restituisce direttamente un valore. Il simbolo => associa ogni caso al suo risultato e il carattere di sottolineatura rappresenta il caso predefinito.

// La switch expression restituisce un valore
string dayType = day switch
{
    1 or 2 or 3 or 4 or 5 => "Feriale",
    6 or 7 => "Weekend",
    _ => "Non valido"
};

Il ciclo for

Il ciclo for è ideale quando si conosce in anticipo il numero di ripetizioni. È composto da tre parti separate da punto e virgola: l'inizializzazione, la condizione di continuazione e l'aggiornamento eseguito al termine di ogni iterazione.

// Stampa i numeri da 0 a 4
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

I cicli while e do-while

Il ciclo while ripete un blocco finché una condizione resta vera, valutandola prima di ogni iterazione. Il ciclo do-while è simile, ma valuta la condizione alla fine, garantendo così che il blocco venga eseguito almeno una volta.

// while: verifica la condizione prima di eseguire
int count = 0;
while (count < 3)
{
    Console.WriteLine($"Iterazione {count}");
    count++;
}

// do-while: esegue almeno una volta
int attempts = 0;
do
{
    Console.WriteLine("Tentativo eseguito");
    attempts++;
} while (attempts < 1);

Il ciclo foreach

Il ciclo foreach attraversa gli elementi di una collezione senza dover gestire manualmente un indice. È la forma preferita quando si vuole semplicemente esaminare ogni elemento in sequenza, perché è più chiara e meno soggetta a errori.

string[] languages = { "C#", "Go", "Python" };

foreach (string language in languages)
{
    Console.WriteLine(language);
}

Controllare il flusso: break e continue

All'interno di un ciclo, l'istruzione break interrompe immediatamente il ciclo, mentre continue salta il resto dell'iterazione corrente e passa alla successiva. Entrambe permettono un controllo fine del flusso.

for (int i = 0; i < 10; i++)
{
    if (i == 5)
    {
        break; // interrompe il ciclo quando i vale 5
    }

    if (i % 2 == 0)
    {
        continue; // salta i numeri pari
    }

    Console.WriteLine(i); // stampa solo 1 e 3
}

Conclusione

Abbiamo visto come gli operatori aritmetici, relazionali e logici permettano di calcolare e confrontare i valori, e come gli operatori dedicati ai nulli rendano il codice più conciso e sicuro. Abbiamo poi esplorato le strutture di controllo che governano il flusso: le decisioni con if e switch, incluse le loro forme moderne come espressioni, e i cicli for, while, do-while e foreach, insieme al controllo fine offerto da break e continue. Nel prossimo articolo impareremo a organizzare il codice in metodi, per renderlo riutilizzabile e ben strutturato.