C#

STRUTTURE DI CONTROLLO

Anche C# non si sottrae a questa parte forse un po' tediosa ma terribilmente utile. Un piccolo vantaggio è dato dal fatto che siamo di fronte a concetti molto semplici ed intuitivi che vi verranno utili anche quando li dovrete applicare in altri linguaggi i cui costrutti vi saranno probabilmente molto famigliare dopo aver appreso quelli di C# (e viceversa, of course)

IF
La prima istruzione di cui ci occupiamo  permette di effettuare delle selezioni basandoci sulla valutazione di una espressione booleana. Si tratta, breve e concisa, di if. La sua sintassi, che coinvolge nel complesso oltre alla keyword if anche else,  può essere espressa in 4 modi:

1)
if (condizione booleana)
istruzioni

2)
if(condizione booleana)
istruzioni
else
istruzioni

3)
if (condizione booleana)
istruzioni
else if (condizione booleana)
istruzioni
else if(condzione booleana)
istruzioni

4)
if (condizione booleana)
istruzioni
else if (condizione booleana)
istruzioni
else if(condzione booleana)
istruzioni
else
istruzioni

i casi 2 e 4 sono semplicemente estensioni di casi 1 e 3 con l'inserimento di un "ramo" in più, quello finale, che funziona da rastrello per raccogliere tutti i casi che non sono compresi nelle condizioni precedenti. Nei casi 3 e caso i rami che iniziano con else if possono essere ovviamente più di 2 anche se, per leggibilità e manutenibilità, è bene non esagerare con i casi da esaminare. Se questi sono troppo numerosi ci si può rivolgere a switch, che vedremo più avanti in questo stesso paragrafo oppure, in qualche caso,  esiste un'altra possibilità di cui parleremo insieme ai dictionaries. Riguardo agli eleemnti costitutivi abbiamo:
--- la condizione booleana è un'espressione che restituisce true o false. Non sono ammessi valori come 0 o 1, diveramente da quanto avviene in C/C++, e questo è un bene. A questo proposito tenete presente che il concetto appena espresso ha una valenza in tutto paragrafo (e in C# in generale): quando parliamo di espressione booleana parliamo di "qualcosa" che retituisce true o false, senza eccezioni: Il fatto che si debba ricavare solo ed esclusivamente un valore booleano pure ci tiene anche a distanza da bug fastidiosi, come vedremo. E' obbligatorio, come da sintassi, mettere la condizione booleana tra parentesi.
--- L'indicazione relativa alla "espressione" significa semplicemente una sequenza di istruzioni che può essere costituita da una riga o da più righe, in questo secondo caso però le istruzioni essere devono essere racchiuse tra le classiche parentesi graffe.
If è da intendersi ovviamente come "se si verifica una certa condizione esegui il codice".
Ora vediamo qualche esempio:

  Esempio 10.1
1
2
3
4
5
6
7
8
9
10
11
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero: ");
    int x = int.Parse(Console.ReadLine());
    if (x > 0) Console.WriteLine("Numero maggiore di 0");
  }
}

Si tratta dell'applicazione del caso 1 con una istruzione singola.

  Esempio 10.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero: ");
    int x = int.Parse(Console.ReadLine());
    if (x > 0) Console.WriteLine("Numero maggiore di 0");
    else
    {
      Console.WriteLine("Inserito numero < o = 0");
      Console.WriteLine("Preferisco i numeri positivi");
    }
  }
}

Qui abbiamo implementato il caso 2 con più istruzioni nel brach relativo a else, istruzioni che quindi, come detto, vanno inserite nella consueta coppia di parentesi graffe, che trovate alle righe 11 e 14, per intenderci.

  Esempio 10.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero: ");
    int x = int.Parse(Console.ReadLine());
    if (x > 0) Console.WriteLine("Numero maggiore di 0");
    else if (x == 0)
    {
      Console.WriteLine("Inserito numero 0");
      Console.WriteLine("Proprio quello eh...");
    }
    else
    {
      Console.WriteLine("Inserito numero negativo");
      Console.WriteLine("Porta la tua negativita' lontano da qui!");
    }
  }
}

Qui implementiamo il caso 3. Lascio a voi il divertimento di provare un esempio col caso 4, la logica dovrebbe essere chiara. E' interessante notare la riga 10. In essa viene effettuato un confronto tramite l'operatore == e fin qui nulla di strano. Quello che è positivo è che, al contrario di quanto accade in C/C++ non passa l'espressione (x = 0), che è un'assegnazione, invece di (x == 0) che è un confronto. Questo piccolo accorgimento ci garantisce la sicurezza contro fastidiosi bugs dovuti a banali errori di digitazione. Ecco quindi un'applicazione del type safety di C#. Provare per credere.
La scelta delle condizioni booleana va effettuata con accuratezza da parte del programmatore in modo da coprire tutte le possibilità che devono essere esaminate nel suo problema. Va da sè che l'esecuzione di un ramo esclude tutti gli altri ad esso successivi.
E' possibile nidificare più livelli di if:

  Esempio 10.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero: ");
    int x = int.Parse(Console.ReadLine());
    Console.Write("Inserisci un altro numero: ");
    int y = int.Parse(Console.ReadLine());
    if (x > 10)
      if (y > 10) Console.WriteLine("Entrambi maggiori di 10");
      else Console.WriteLine("Solo il primo maggiore di 10");
   else if (y > 10) Console.WriteLine ("Solo il secondo maggiore di 10");
   else Console.WriteLine("Entrambi minori o uguali a 10");
  }
}

Come è evidente, l'indentazione ci aiuta molto nel capire la corrispondenza tra i vari if ed else. In questo caso quindi l'istruzione else alla 13esima riga è legata al if che si trova alla 12 mentre gli else alla 14 ed alla 15 si connettono con if che sta alla 11. Attenzione a come costruite queste nidificazioni.
L'istruzione if è estremamente utile ed importante, la troverete molto simile in tantissimi altri linguaggi di programmazione. Eppure il suo uso deve essere fatto cum grano salis in quanto è abbsatzna facile rendere pesante il vostro codice, come già accennato. Esiste anche un "movimento" contro l'uso di questa istruzione... folcloristico ma in qualche modo significativo.
Quando parleremo degli operatori incontreremo una sintassi alternativa basata sull'operatore ?.

SWITCH (e goto)

Compagno di merende ideale per if è l'istruzione switch che coinvolge nella sua sintassi anche case, break e default. Come dice il nome stesso (switch in inglese vuole dire tante cose tra cui anche interruttore, commutatore o anche scambiare, smistare, forma verbale) si tratta di una istruzione per effettuare una selezione e si propone come importante alternativa ad if nel caso in cui ci siano da considerazione numerose ramificazioni. La sintassi è la seguente:

switch (selettore)
{
case valore-1:
istruzioni;
break;
case valore-2:
istruzioni;
break;
.....
case valore-n:
istruzioni;
break;
default:
istruzioni;
break;
}

Andiamo con ordine:

--- Il selettore è una variabile di tipo numerico o stringa.
--- La keyword case introduce i valori di confronto per la variabile. Se il confornto ha esito positivo vengono eseguite le relative istruzioni altrimenti si passa al caso successivo
--- La keyword break indica la fine del blocco, è obbligatoria per ogni case e anche per il default finale
--- Default è il rastrello che comprende tutti i casi non contemplati nei precedenti branch.

Vediamo un esempio:

  Esempio 10.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero che sia 1 o 2 o 3: ");
    int x = int.Parse(Console.ReadLine());
    switch (x)
    {
      case 1:
        Console.WriteLine("Hai inserito il numero più basso");
        break;
      case 2:
        Console.WriteLine("Hai inserito il numero intermedio");
        break;
      case 3:
        Console.WriteLine("Hai inserito il numero più alto");
        break;
      default:
        Console.WriteLine("Ho detto: 1 o 2 o 3");
        break;
    }
  }
}

Dalla riga 9 alla 23 abbia il nostro blocco in pieno svolgimento. La sintassi è quella evidenziata. Da notare che se abbiamo bisogno di più istruzioni non è necessario usare le parentesi graffe. Ad esempio:

case 2:
Console.WriteLine("Hai inserito il numero intermedio");
Console.WriteLine("In medio stat virtus");
Console.WriteLine("1+3=4/2=2");
break;

funziona perfettamente.
A volte può essere necessario dare lo stesso comportmento a più valori del selettore. Esiste una sintassi ad hoc che, a parer mio, non è quel gran che, come si può vedere se provate a modificare l'esempio precedente come segue:

switch(x)
{
  case 1:
  case 2:
  case 3:
  case 4:
    istruzioni;
    break;
  case 5:
    istruzioni
  default:
    istruzioni
    break:
}

Ovvero: nel caso in cui il valore vada da 1 a 4 avremo il comportamento guidato dalle istruzioni relative a case 4:
come detto, questa sintassi non mi piace, altri linguaggi propongono codice tipo:
case 1,2,3,4
a mio avviso più chiaro e sintetico.

La funzione del break è resa in qualche modo più palese dal coice precedente; il suo scopo è quello di impedire il fall-through, ovvero la possibilità che, eseguito un ramo, si possa ricadere in quello successivo, il che è possibile, e fonte di guai, in altri linguaggi. Il compilatore vuole sempre che questa possibilità sia evitata quindi non vi permette di evitare il brak tranne che nel caso di più valore aventi un unico comportamento. Tuttavia è accettabile anche scrive qualcosa tipo:

case 3:
while (true) Console.WriteLine("evviva");


ovvero, è sarà più chiaro quando, tra breve, illustreremo while, esiste un loop infinito che impedisce il raggiugimento di altro codice. Anche in questo caso si può modificare l'esempio 10.5 ed eventualmente provate a vedere cosa succede se elimante il while(true). Anzi ve lo dico io, il compilatore dice chiaramente cosa non va:

error CS0163: Il controllo non può passare da un'etichetta
case ('case 3:') a un'altra.


Come abbiamo detto, il salto da un ramo ad un altro non si può fare ed ebene, di norma, che sia evitato, anche per questioni di leggibilità del vostro codice. Se per qualche motivo decideste invece che proprio non potete farne a meno esiste un sistema che, tanto per confermare quanto sia rara questa eventualità, fa uso della vituperata istruzione goto. Su questa povera keyword si accaniscono soprattutto gli aspiranti "guru" della programmazione, esiste un odio generalizzato nei suoi confronti e un sarcasmo esagerato verso chi si permette di usarla. A dire il vero posso confermare che un paio di programmatori di un qualche spessore mi hanno fatto notare una certa sua espressività all'interno del codice, in quanto attira l'attenzione proprio per la sua "rarità". Forse un po' di ironia c'era ma non hanno fatto poi tutte ste faccie orripilate che vedrete tra i giovani emergenti quando sentono queste 4 lettere in sequenza: g o t o. Ad ogni modo quasi tutti i compilatori se la tengono a loro uso e consumo non si sa mai. Ovviamente non voglio mettermi contro gente come Tanenmabum e Jacopini-Bohem, leader nel dilagante movimento anti-goto, (infatto l'ho messo in minuscolo in apertura del paragrafo) ma insomma, non si deve demonizzare nulla. E quanto segue ne è la prova.
Ad ogni modo, goto obbliga il codice a saltare da un'altra parte del programma, sempre allo stesso livello e funziona in congiunzione con una label, una etichetta che sarà costituita da un identificativo univoco nel vostro programma seguito da : , che indica dove saltare. Ovvero:

nomelabel:
....
goto nomelabel;


In congiunzione con switch può servire per abilitare delle ramificazioni che altrimenti avremmo saltato. La label può anche essere messa più avanti rispetto al goto a livello di codice cioè il salto può avvenire sia in avanti che, come nella definizione formale, all'indietro,, ma deve essere usata una volta sola all'interno del programma, naturalmente. Vediamo l'esempio:

  Esempio 10.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero che sia 1 o 2: ");
    int x = int.Parse(Console.ReadLine());
    switch (x)
    {
      case 1:
        label1:
        Console.WriteLine("Inserito un valore maggiore di 0");
        break;
      case 2:
        Console.WriteLine("Inserito un valore maggiore di 1");
        goto label1;
        break;
      default:
        Console.WriteLine("Non ci siamo...");
        break;
    }
  }
}

Se eseguito questo programma ed immette il valore 2 verrà eseguito il codice relativo al case corretto ma, a seguito dell'istruzione goto verrà lanciata anche l'esecuzione di della riga 13, che verrebbe altrimenti lanciata solo se immette il valore 1.
Ora potete dimenticare goto, così vi sentirete dei veri draghi della programmazione....

Per finire e per completezza, diamo un esempio in cui viene presa in esame una stringa:

  Esempio 10.7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Squadra di calcio preferita: ");
    string sq = Console.ReadLine();
    switch (sq)
    {
      case "Juventus":
        Console.WriteLine("Ottima scelta!");
        break;
      case "Milan":
        Console.WriteLine("Sicuro? Che brutta cosa...");
        break;
      case "Inter":
        Console.WriteLine("Ti piace soffrire e perdere eh?");
        break;
     default:
        Console.WriteLine("Cosa??");
        break;
      }
   }
}

Switch è una istruzione potente ed elegante che permette una ,iglior leggibilità e manutenibilità rispetto a lunghe catene di if- else, anche se if resta preferibile con casisstiche ristrette. Lo incontrerete spesso nella vostra esprienza di programmazione.

FOR

Iniziamo a parlare ora, dopo aver visto quelle di selezione, delle istruzioni di iterazione (o cicli o loop). Queste sono presenti nel mondo della programmazione si può dire da sempre e quindi, come al solito, non vediamo nulla di particolarmente innovativo. Però è anche vero che si tratta di concetti universalmente utili.

In particolare for è molto semplice e mol, molto utile. La sintassi è la seguente:

for (condizione)
istruzioni


laddove la condzione ha il seguente formato:
variabile inzializzata; condizione booleana; incremento / decremento)
mente le istruzioni possono essere una sola o più racchiuse in questo caso obbligatoriamente all'interno delle solite graffe.

Un esempio chiarirà subito questa semplice istruzione.

  Esempio 10.8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;

class Test
{
  public static void Main()
  {
    Console.Write("Inserisci un numero: ");
    int x = int.Parse(Console.ReadLine());
    for (int y = 0; y < x; y++)
    {
      Console.WriteLine(y + x);
      Console.WriteLine(x - y);
    }
  }
}

La riga 9 è ovviamente quella interessante. Come si vede, tra parentesi abbiamo:

-- una dichiarazione di variabile (int y = 0)
-- una espressione booleana che viene controllata ad ogni iterazione (equivale ad if (y < x))
-- un incremento sulla variabile

quando y raggiunge il valore di x l'esecuzione del codice alla riga 11 e alla 12 viene fermata. Come vedete concettualmente è molto semplice ma anche in pratica. La condizione introdotta da for, in questo esempio facilmente generalizzabile, si potrebbe parafrasare come: "per y che parte da 0, finchè y è minore di x, incrementando y ad ogni ciclo di una unità"
Interessante caso mai è vedere che fine fa la variabile y. Parleremo specificamente dell'argomento "scope", termine inglese traducibile come "visibilità" per le variabili. Qui anticipiamo che y esiste solo all'interno del ciclo for, ovvero è utilizzabile esclusivamente all'interno di esso. Al dif fuori non esiste ed ogni tentativo di riferimento ad essa darebbe origine ad un errore molto chiaro da parte del compilatore.

L'incremento può essere maggiore di 1, ovviamente
y = y + 2
o anche essere negativo
y = y - 1
oppure anche una cosa del tipo:
y = (y * 2) + 3

Utile, anche se un po' più complessa e molto raramente usata, è la possibilità di lavorare su due variabili a tempo:

  Esempio 10.9
1
2
3
4
5
6
7
8
9
10
11
12
using System;

class Test
{
  public static void Main()
  {
    for (int x = 0, y = 0; x <= 10 && y <= 10; x++, y++)
    {
      Console.WriteLine(x + y);
    }
  }
}

E' possibile creare schemi anche più complessi ma per ora direi che basta così.

Il nostro for si presta anche bene a gestire cicli innestati:

  Esempio 10.10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;

class Test
{
  public static void Main()
  {
    for (int x = 0; x <= 10; x++)
    {
      for (int y = 0; y < 10; y++)
      {
        Console.WriteLine(x + y);
      }
    Console.WriteLine(x);
    }
  }
}

La variabile y, per quanto detto, non è visibile dal for che inizia alla riga 7 mentr x lo è all'interno di quello che inizia alla 9.
Se volete avere piena visibilità delle variabili che usate nei vostri for dovrete dichiararle prima dei cicli stessi ad un livello più alto.

  Esempio 10.11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

class Test
{
  public static void Main()
  {
    int x = 0;
    int y = 0;
    for (x = 0; x <= 10; x++)
    {
      for (y = 0; y <= 10; y++)
      {
        Console.WriteLine(x + y);
      }
    Console.WriteLine(x);
    }
  Console.WriteLine(x + y);
  }
}

In questo caso x + y alla riga 17 varrà 22.

Come si scrive un loop infinito con C#? Molto semplicemente con:

for(;;)


così facendo eviterete brutture come quelle che ho visto in giro,  tipo:

for (int x = 5; x < 3; x++)

che lascio a voi commentare....

NB: a questo punto si potrebbe introdurre lìistruzione foreach che è legata in modo stretto a for, tanto da essere a volte sovrapponibili. Molti autori lo fanno, nei loro testi. Senza voler contraddire questi professionisti, da parte mia ritengo che dal momento che foreach è strettamente legato ad alcune strutture dati, comunque non è certamente generalista come for, è bene parlarne quando affronteremo queste strutture (array, collezioni ecc) per non aggiungere ulteriore confusione.

WHILE

Ecco un'altra istruzione storica. Il funzionamento è del tutto simile e spesso sovrapponibile con quello di for.  La sintassi è ancora più semplice:

while (espressione booleana)
istruzioni


L'spressione booleana è analoga a quanto già visto che per altre istruzioni.
Subito l'esempio:

  Esempio 10.12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System;

class Test
{
  public static void Main()
  {
    int x = 0;
    while (x < 5)
    {
      Console.WriteLine(x);
      x++;
    }
  }
}

La riga 7 inizializza la variabile che sarà usata nel loop. A differenza di for usando il while non è  permesso inizlizzare nuove variabili all'interno della condizione booleana. Inoltre un eventuale incremento deve essere gestito all'interno delle istruzioni, una o più tra parentesi graffe, che sottendono il while stesso. Se non vi fosse l'istruzione ala riga 11 il valore di x rimarrebbe 0 e il ciclo risulterebbe infinito dal momento che x risulterebbe sempre < 0. La condizione introdotta da while infatti sta per "finchè è vero che...".
Due considerazioni sono da fare:
1) bisogna naturalmente fare attenzione al verificarsi dell'uscita dal loop
2) analogamente se la condizione non è verificata dall'inizio il codice all'interno del while non andrà mai in esecuzione. Ad esempio se abbiamo:

int x = 3;
while (x < 2)
....


ecco che il codice interno al while non viene eseguito.
Realizzare un loop infinito con while è banale, anche qui nn è necessario ricorrere a chissà quale porcheria:

while(true)
......

e il gioco è fatto.
Per chiudere questa semplice istruzione riporto un esempio che usa due variabili:

  Esempio 10.13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

class Test
{
  public static void Main()
  {
    int x = 0;
    int y = 0;
    while (x < 5 && y < 4)
    {
      Console.WriteLine(x);
      Console.WriteLine(x);
       x++;
      y++;
    }
  }
}


DO WHILE

Del tutto simile a while l'istruzione do while segue la stessa logica ma con qualche differenza. Vediamo la sintassi:

do
istruzioni
while (espressione booleana)


Ovvero esegue il blocco di istruzioni finche l'espressione booleana risulta true.

  Esempio 10.14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;

class Test
{
  public static void Main()
  {
    int x = 0;
    do
    {
       Console.WriteLine(x);
       x++;
    }
    while (x < 5);
  }
}

La differenza fondamentale è che il blocco di istruzioni viene eseguito sempre almeno una volta anche se la condizione che segue while non è verificata. Questo perchè, come è evidente, la sua valutazione segue il blocco di istruzioni.
Le considerazioni sono, per il resto, del tutto analoghe a quanto visto per il while.

BREAK E CONTINUE

Queste istruzioni non riguardano propriamente i loop o le selezioni, ma interagiscono con esse in quanto sono in grado di modificare il normale funzionamento delle iterazioni. In particolare:

--- break interrompe il flusso dell'iteazione e salta fuori da essa
--- continue salta un ciclo passando a quello successivo.

D'altronde break lo abbiamo già visto all'azione parlando di switch e ricordete che, in pratica, costringeva il codice a saltare fuori dallo switch stesso.
Vediamoli in azione con due esempi:

  Esempio 10.15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;

class Test
{
  public static void Main()
  {
    int x = 0;
    for(;;)
    {
      Console.WriteLine(x);
      x = x + 1;
      if (x == 10) break;
    }
  }
}

Il programma 10.15 prevede un ciclo infinito (riga 8, ricordate?). Questo non sarebbe mai interrotto (fino ad un probabile overflow) se non fosse che alla riga 12 abbiamo detto in pratica: quando raggiungi il valore 10 esci. Ed è sattamente ciò che accade se eseguite il programma, il programma stampa il numero 9 poi, dal momento che viene raggiunto il valore 10, si esce dall'iterazione e si torna al livello superiore, quindi il programma termina. E' importante ribadire che si torna al livello immediatamente superiore non si saltà più lontano di lì. Altri linguaggi permettono salti più coraggiosi ma in C# ci teniamo stretti questo approccio a mio avviso equilibrato.

Passiamo a continue:

  Esempio 10.15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;

class Test
{
  public static void Main()
  {
    int x = 0;
    while (x < 10)
    {
      x = x + 1;
      if (x == 5) continue;
      Console.Write(x + " ");
    }
  }
}

Qui invece vediamo in azione continue e l'output ci suggerisce cosa succede:

1 2 3 4 6 7 8 9 10

Vedete? Manca il 5. Il codice di sopra infatti impone che, quando viene raggiunte tale valore, riga 11, il programma deve continuare saltando all'iterazione successiva. Quindo mentre break ci fa bruscamente interrompere il ciclo, continue ce ne fa saltare una parte, come avevamo già detto.