Espressioni Logiche in C#

Finora nelle nostre lezioni abbiamo visto come realizzare condizioni semplici all'interno delle istruzioni condizionali in C#.

Ci siamo limitati a verificare se una variabile è uguale a un valore specifico, ad esempio voto == 100, e abbiamo visto come verificare più condizioni in sequenza con l'istruzione else if.

Adesso, per realizzare condizioni più complesse, dobbiamo imparare a costruire espressioni logiche in C#.

Espressioni Logiche

Nelle lezioni precedenti abbiamo studiato le istruzioni condizionali if e else in linguaggio C#.

In particolare abbiamo visto che la sintassi di un'istruzione if è la seguente:

if (condizione)
{
    // Blocco di codice se la condizione è vera
}

L'espressione condizione viene valutata dall'istruzione if e determina se il blocco di codice all'interno delle parentesi graffe deve essere eseguito o meno.

Finora non siamo entrati nel dettaglio di come debba essere costruita l'espressione condizione e ci siamo limitati a considerare espressioni semplici come voto == 100, ossia ci siamo limitati a verificare se una variabile è uguale a un valore specifico.

In generale, le istruzioni condizionali come l'istruzione if accettano un'espressione che restituisce uno tra due possibili valori: true o false. Tali espressioni prendono il nome di espressioni logiche.

Definizione

Espressione Logica

In C#, un'espressione logica è un'espressione il cui risultato è di tipo booleano, ossia può assumere solo i due possibili valori true o false.

Per costruire un'espressione logica non esiste soltanto l'operatore di uguaglianza, che abbiamo già visto, ma esistono diversi operatori che permettono di confrontare valori e variabili in modo da ottenere un risultato booleano.

Questi operatori si dividono in due categorie:

  • Operatori relazionali: permettono di confrontare due espressioni e restituiscono un valore booleano;
  • Operatori logici: permettono di combinare più espressioni logiche e restituiscono un valore booleano.

Analizziamo queste due categorie di operatori nel dettaglio.

Operatori Relazionali

Gli operatori relazionali, come indica il nome, sono operatori che confrontano, ossia mettono in relazione due valori e restituiscono un valore booleano a seconda del risultato del confronto.

Il primo operatore relazionale che abbiamo già incontrato è l'operatore di uguaglianza ==, che permette di verificare se due valori sono uguali. Ad esempio:

int voto = 100;
if (voto == 100)
{
    Console.WriteLine("Il voto è 100");
}

In questo caso, l'espressione voto == 100 restituisce true se la variabile voto è uguale a 100, altrimenti restituisce false.

Da notare che l'operatore di uguaglianza si scrive con due simboli di uguale == e non con uno solo come l'operatore di assegnamento =.

Complementare all'operatore di uguaglianza è l'operatore di disuguaglianza !=, che restituisce true se due valori sono diversi. Ad esempio:

int voto = 100;
if (voto != 0)
{
    Console.WriteLine("Il voto è diverso da 0");
}

In questo caso, l'espressione voto != 0 restituisce true se la variabile voto è diversa da 0, altrimenti restituisce false.

Gli altri operatori relazionali sono i seguenti:

  • Operatore di maggiore >: restituisce true se il primo valore è maggiore del secondo;
  • Operatore di minore <: restituisce true se il primo valore è minore del secondo;
  • Operatore di maggiore o uguale >=: restituisce true se il primo valore è maggiore o uguale al secondo;
  • Operatore di minore o uguale <=: restituisce true se il primo valore è minore o uguale al secondo.

Attraverso questi sei operatori siamo in grado di confrontare due valori e ottenere un risultato booleano.

Di seguito è riportata una tabella riassuntiva degli operatori relazionali:

Operatore Descrizione Esempio
== Uguaglianza a == b
!= Disuguaglianza a != b
> Maggiore a > b
< Minore a < b
>= Maggiore o uguale a >= b
<= Minore o uguale a <= b
Tabella 1: Operatori relazionali in C#
Definizione

Operatori Relazionali in C#

Gli Operatori Relazionali in C# permettono di confrontare due espressioni e restituiscono un valore booleano.

Tutti gli operatori relazionali sono binari in quanto richiedono due operandi.

La sintassi generale di un'espressione logica con un operatore relazionale è la seguente:

espressione1 operatore espressione2

dove espressione1 e espressione2 sono due espressioni qualsiasi e operatore è uno degli operatori relazionali disponibili.

Gli operatori relazionali disponibili in C# sono i seguenti:

  • L'operatore di uguaglianza == restituisce true se due valori sono uguali;
  • L'operatore di disuguaglianza != restituisce true se due valori sono diversi;
  • L'operatore di maggiore > restituisce true se il primo valore è maggiore del secondo;
  • L'operatore di minore < restituisce true se il primo valore è minore del secondo;
  • L'operatore di maggiore o uguale >= restituisce true se il primo valore è maggiore o uguale al secondo;
  • L'operatore di minore o uguale <= restituisce true se il primo valore è minore o uguale al secondo.

Operatori Logici

Gli operatori relazionali non bastano a coprire l'intera gamma di espressioni logiche che si possono costruire. Per esempio, potremmo voler verificare se due condizioni sono entrambe vere o se almeno una delle due è vera.

Ritornando all'esempio del voto, potremmo voler verificare se il voto è compreso tra 80 e 100 per poter assegnare cinque stelle al film. In questo caso, dovremmo verificare se il voto è maggiore o uguale a 80 e minore o uguale a 100. In pratica, si tratta di combinare due espressioni logiche di confronto:

  • voto >= 80: restituisce true se il voto è maggiore o uguale a 80;
  • voto <= 100: restituisce true se il voto è minore o uguale a 100.

Per questo motivo, il C#, così come altri linguaggi di programmazione, mette a disposizione degli operatori logici che permettono di combinare più espressioni logiche e ottenere un risultato booleano.

Nella tabella che segue sono riportati gli operatori logici disponibili in C#:

Operatore Descrizione Esempio Cardinalità
&& AND a && b Binario
|| OR a || b Binario
! NOT !a Unario
Tabella 2: Operatori logici in C#

Da notare che nella tabella di sopra abbiamo riportato anche la cardinalità degli operatori, ossia il numero di operandi che ciascun operatore richiede. Gli operatori && e || sono binari, ossia richiedono due operandi, mentre l'operatore ! è unario, ossia richiede un solo operando.

Analizziamoli nel dettaglio.

Operatore AND

L'operatore AND && permette di combinare due espressioni logiche e restituisce true solo se entrambe le espressioni sono vere. Ad esempio:

int voto = 90;
if (voto >= 80 && voto <= 100)
{
    Console.WriteLine("Il voto è compreso tra 80 e 100");
}

In questo caso, l'espressione voto >= 80 && voto <= 100 restituisce true se il voto è maggiore o uguale a 80 e minore o uguale a 100, altrimenti restituisce false.

In poche parole, se una o entrambe le espressioni sono false, l'operatore AND restituisce false.

Per esprimere questo concetto possiamo usare una tabella di verità, ossia una tabella che mostra tutti i possibili valori di verità di due espressioni logiche combinate con l'operatore AND:

a b a && b
false false false
false true false
true false false
true true true
Tabella 3: Tabella di verità dell'operatore AND
Definizione

Operatore AND in C#

L'Operatore AND && in C# permette di combinare due espressioni logiche e restituisce true solo se entrambe le espressioni sono vere.

La sintassi generale di un'espressione logica con l'operatore AND è la seguente:

espressione1 && espressione2

dove espressione1 e espressione2 sono due espressioni logiche qualsiasi.

Operatore OR

L'operatore OR || permette di combinare due espressioni logiche e restituisce true se almeno una delle due espressioni è vera. Ad esempio, supponiamo di voler verificare se il voto è esattamente uguale a 100 o a 0:

int voto = 100;

if (voto == 100 || voto == 0)
{
    Console.WriteLine("Il voto è 100 o 0");
}

In questo caso, l'espressione voto == 100 || voto == 0 restituisce true se il voto è uguale a 100 o a 0, altrimenti restituisce false.

In poche parole, se una o entrambe le espressioni sono vere, l'operatore OR restituisce true.

Analogamente all'operatore AND, possiamo esprimere il funzionamento dell'operatore OR attraverso una tabella di verità:

a b a || b
false false false
false true true
true false true
true true true
Tabella 4: Tabella di verità dell'operatore OR
Definizione

Operatore OR in C#

L'Operatore OR || in C# permette di combinare due espressioni logiche e restituisce true se almeno una delle due espressioni è vera.

La sintassi generale di un'espressione logica con l'operatore OR è la seguente:

espressione1 || espressione2

dove espressione1 e espressione2 sono due espressioni logiche qualsiasi.

Operatore NOT

L'operatore NOT ! è l'unico operatore logico unario, ossia accetta un solo operando. Esso viene, infatti, applicato all'espressione logica che lo segue subito dopo e ne inverte il valore di verità.

In altre parole, se l'espressione logica è vera, l'operatore NOT la trasforma in falsa e viceversa.

Ad esempio, supponiamo di voler verificare se il voto non sia uguale a 100:

int voto = 90;

if (!(voto == 100))
{
    Console.WriteLine("Il voto non è 100");
}

In questo caso, l'espressione !(voto == 100) restituisce true se il voto non è uguale a 100, altrimenti restituisce false.

La tabella di verità per l'operatore NOT è molto semplice in quanto accetta un solo operando:

a !a
false true
true false
Tabella 5: Tabella di verità dell'operatore NOT
Definizione

Operatore NOT in C#

L'Operatore NOT ! in C# permette di invertire il valore di verità di un'espressione logica.

La sintassi generale di un'espressione logica con l'operatore NOT è la seguente:

!espressione

dove espressione è un'espressione logica qualsiasi.

Cortocircuito degli Operatori Logici

Un'importante proprietà degli operatori logici binari in C#, AND e OR, è il cosiddetto cortocircuito.

Per comprendere appieno in cosa consista prendiamo un esempio. Supponiamo di avere due espressioni logiche combinate con l'operatore AND:

if (condizione1 && condizione2)
{
    // Blocco di codice
}

Per come funziona l'operatore AND, &&, il blocco di codice verrà eseguito solo se entrambe le condizioni condizione1 e condizione2 risultano vere.

All'atto pratico, il programma, per stabilire se il blocco di codice debba essere eseguito, valuta prima condizione1 e successivamente condizione2. Solo nel caso in cui entrambe siano vere, il blocco di codice viene eseguito.

Tuttavia, un'ottimizzazione che il compilatore C# effettua in questo caso è quella di non valutare la seconda condizione se la prima è falsa. In altre parole, non ha senso valutare condizione2 se condizione1 è falsa, poiché il risultato dell'operazione AND sarà comunque falso:

condizione1 condizione2 Risultato
true valutata valore di condizione2
false non valutata false
Tabella 6: Cortocircuito per l'operatore AND

Questo comportamento prende il nome di cortocircuito proprio perché è come se il programma saltasse la valutazione della seconda condizione se la prima è falsa.

Analogamente, per l'operatore OR, ||, il blocco di codice verrà eseguito se almeno una delle due condizioni condizione1 e condizione2 è vera. Anche in questo caso, se la prima condizione è vera, non ha senso valutare la seconda condizione, poiché il risultato dell'operazione OR sarà comunque vero:

condizione1 condizione2 Risultato
true non valutata true
false valutata valore di condizione2
Tabella 7: Cortocircuito per l'operatore OR

Il cortocircuito è un'ottimizzazione che permette di risparmiare tempo e risorse computazionali, evitando di valutare espressioni logiche inutili. Inoltre permette, come vedremo, di scrivere codice più conciso.

Ad esempio, supponiamo di voler verificare se un file esiste e, in caso affermativo, eseguire un blocco di codice se il file ha una lunghezza maggiore di 100 byte. A questo punto della guida non abbiamo ancora studiato i file, ma possiamo immaginare di avere due funzioni FileExists e FileSize che restituiscono rispettivamente true se il file esiste e la sua dimensione in byte.

Possiamo scrivere il codice in questo modo:

if (FileExists("file.txt") && FileSize("file.txt") > 100)
{
    // Blocco di codice
}

Grazie al cortocircuito, se la funzione FileExists restituisce false, la funzione FileSize non verrà mai chiamata, evitando di eseguire un'operazione su un file inesistente che provocherebbe un errore.

Definizione

Cortocircuito degli Operatori Logici

In C# gli operatori logici binari AND && e OR || supportano il cortocircuito.

Nel caso dell'operatore AND, &&, se la prima espressione è falsa, la seconda non viene valutata poiché il risultato dell'operazione sarà comunque falso.

Nel caso dell'operatore OR, ||, se la prima espressione è vera, la seconda non viene valutata poiché il risultato dell'operazione sarà comunque vero.

Bool e Espressioni Logiche

Come abbiamo detto, il risultato di una qualunque espressione logica in C# è di tipo booleano, ossia di tipo Bool.

Pertanto, oltre che nelle istruzioni condizionali, il risultato di un'espressione logica piò anche essere memorizzato in una variabile di tipo Bool.

Ad esempio, supponiamo di voler verificare se un numero è pari e memorizzare il risultato in una variabile:

int numero = 10;

bool pari = numero % 2 == 0;

if (pari)
{
    Console.WriteLine("Il numero è pari");
}

In questo caso, la variabile pari conterrà il valore true se il numero è pari, altrimenti conterrà il valore false.

In generale, memorizzare il risultato di un'espressione logica in una variabile di tipo Bool può rendere il codice più chiaro e leggibile, perché possiamo spezzare un'espressione complessa in espressioni più semplici e memorizzarle in variabili con nomi significativi.

Ad esempio, supponiamo di voler realizzare un programma che stampi a schermo un messaggio solo se un numero, inserito dall'utente, è compreso tra 1 e 100 e se è dispari. Possiamo scrivere il codice in questo modo:

Console.Write("Inserisci un numero: ");
int numero = Convert.ToInt32(Console.ReadLine());;

bool compresoTra1e100 = (numero >= 1 && numero <= 100);
bool dispari = (numero % 2 != 0);

if (compresoTra1e100 && dispari)
{
    Console.WriteLine("Il numero è compreso tra 1 e 100 ed è dispari");
}

Abbiamo in questo modo semplificato l'espressione logica dell'istruzione if. In caso contrario avremmo dovuto scrivere:

if (numero >= 1 && numero <= 100 && numero % 2 != 0)
{
    Console.WriteLine("Il numero è compreso tra 1 e 100 ed è dispari");
}

Che risulta più difficile da leggere e comprendere.

Definizione

Il risultato di un'espressione logica

Il risultato di un'espressione logica in C# è di tipo Bool e può essere memorizzato in una variabile di tipo Bool.

bool risultato = espressioneLogica;

Precedenza degli Operatori Relazionali e degli Operatori Logici

In questa lezione abbiamo introdotto sei operatori relazionali e tre operatori logici. Tali operatori possono essere combinati con altri operatori matematici per costruire espressioni complesse.

Tuttavia, quando si combinano più operatori in un'espressione, è importante tenere conto della precedenza degli operatori, ossia dell'ordine in cui gli operatori vengono valutati.

Abbiamo già visto come il C# tratti la precedenza degli operatori matematici, ad esempio moltiplicazione e divisione vengono valutate prima di somma e sottrazione.

Adesso vediamo come vengono valutati gli operatori relazionali e logici rispetto agli operatori matematici.

In C# la precedenza viene data agli operatori in questo ordine:

  1. Operatori aritmetici;
  2. Operatori relazionali;
  3. Operatori logici.

Inoltre, all'interno di ciascuna categoria, gli operatori vengono valutati da sinistra a destra.

Proviamo, ad esempio, ad analizzare l'espressione seguente:

int a = Convert.ToInt32(Console.ReadLine());

bool risultato = a * 2 > 5 && a * 2 < 15;

In questo caso, l'espressione a * 2 > 5 && a * 2 < 15 viene valutata in questo modo:

  1. Prima vengono valutate le moltiplicazioni a * 2;
  2. Successivamente vengono valutati gli operatori relazionali > e <;
  3. Infine viene valutato l'operatore logico &&.

Per cui, la variabile risultato sarà vera solo se il doppio di a è compreso tra 5 e 15.

In ogni caso, per evitare ambiguità o problemi di leggibilità è sempre meglio utilizzare le parentesi per chiarire l'ordine di valutazione degli operatori anche nel caso in cui queste non siano necessarie.

Conviene, quindi, riscrivere l'espressione di sopra in questo modo:

bool risultato = ((a * 2) > 5) && ((a * 2) < 15);

In questo modo è chiaro che le moltiplicazioni vanno valutate prima dei confronti e che i confronti vanno valutati prima dell'operatore logico.

Consiglio

Usare le parentesi anche quando non necessarie

Il consiglio che diamo agli sviluppatori è di usare le parentesi per dichiarare esplicitamente l'ordine di valutazione delle espressioni logiche anche quando non strettamente necessarie.

In questo modo si evitano ambiguità e si rende il codice più chiaro e leggibile.

Esempio Completo

Adesso che conosciamo le istruzioni if, le istruzioni else e le espressioni logiche, possiamo finalmente concludere il programma di esempio che abbiamo iniziato a scrivere nella lezione sulle istruzioni if.

Il requisito del programma diceva:

Supponiamo di voler scrivere un programma che prende in ingresso il voto medio dato ad un film e lo converta in una valutazione da 1 a 5 stelle. Il voto di ingresso è espresso con un numero da 0, ossia un film orribile, fino a 100, ossia un film eccezionale. La valutazione in stelle è la seguente:

  • Voto da 0 a 20: ★☆☆☆☆
  • Voto da 21 a 40: ★★☆☆☆
  • Voto da 41 a 60: ★★★☆☆
  • Voto da 61 a 80: ★★★★☆
  • Voto da 81 a 100: ★★★★★

Con le conoscenze acquisite possiamo scrivere il programma in questo modo:

Console.Write("Inserisci il voto medio del film: ");
int voto = Convert.ToInt32(Console.ReadLine());

if (voto >= 0 && voto <= 20)
{
    Console.WriteLine("Valutazione: ★☆☆☆☆");
}
else if (voto >= 21 && voto <= 40)
{
    Console.WriteLine("Valutazione: ★★☆☆☆");
}
else if (voto >= 41 && voto <= 60)
{
    Console.WriteLine("Valutazione: ★★★☆☆");
}
else if (voto >= 61 && voto <= 80)
{
    Console.WriteLine("Valutazione: ★★★★☆");
}
else if (voto >= 81 && voto <= 100)
{
    Console.WriteLine("Valutazione: ★★★★★");
}
else
{
    Console.WriteLine("Il voto non è valido");
}

In Sintesi

In questa lezione abbiamo approfondito il concetto di espressione logica in C#.

Abbiamo visto che un'espressione logica è un'espressione il cui risultato è di tipo booleano, ossia può assumere solo i valori true o false.

Abbiamo introdotto gli operatori relazionali, che permettono di confrontare due valori e restituiscono un valore booleano, e gli operatori logici, che permettono di combinare più espressioni logiche e restituiscono un valore booleano.

Abbiamo analizzato nel dettaglio gli operatori relazionali e logici disponibili in C# e abbiamo visto come sia possibile memorizzare il risultato di un'espressione logica in una variabile di tipo Bool.

Infine, abbiamo discusso della precedenza degli operatori relazionali e logici rispetto agli operatori aritmetici e abbiamo consigliato di usare le parentesi per dichiarare esplicitamente l'ordine di valutazione delle espressioni logiche.

Nella prossima lezione affronteremo un altro importante concetto riguardante le istruzioni if: le istruzioni if innestate.