C#

I METODI

L'argomento oggetto del presente paragrafo risulta di particolare interesse quando si parla delle classi e delle struct. Essi sono fondamentali per consentire di definire il comportamento di quelle strutture dati e per plasmarne le caratteristiche peculiari e sono definiti sempre all'interno delle stesse. Tuttavia possiamo affrontarlo senza troppi timori anche ora trattandolo come fossero un argomento a parte.
I metodi possono essere definiti come dei blocchi di codice ai quali viene attribuito un identificatore e che tramite questo possono essere richiamati ed utilizzati ogni qual volta se ne ha la necessità. Possono richiedere da 0 ad n parametri a seconda di qual è la loro firma (o signature). Tali parametri devono essere foniti, nell'ordine e col tipo richiesti, quando il metodo viene richiamato.
Dal punto di vista formale esse devono avere un livello di accesso, come public o private (questi e altri li ritrovate nel paragrafo dedicato ai modificatori), devono prevedere un valore di ritorno e possono avere opzionalmente dei modificatori, come sealed o abstract. Quindi abbiamo il nome e la lista dei parametri. Riassumendo:

accesso - [modificatore] - valore di ritorno - nome(lista parametri)

Un esempio tipico il classico

public static void Main()

che troviamo come inizio di ogni nostro programma. Vediamo qualche caso più specifico:

  Esempio 15.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
class program
{
  public static void Hello(string nome)
  {
    Console.WriteLine("ciao, " + nome);
  }

  public static void Main()
  {
    Console.Write("Inserisci il tuo nome: ");
    string nome = Console.ReadLine();
    Hello(nome);
  }
}

Il metodo nome è dichiarato pubblico, di modo che l'accesso sia permesso liberamente. E' statico così che possiamo richiamarlo senza necessitare di un'istanza di classe. Questo fatto sarà più chiaro quando parleremo delle classi ma per ora possiamo sorvolare, non ci serve per capire il funzionamento di base dei metodi. Non ha valori di ritorno, come evidenziato da quel void che significa proprio quello, nessun valore di ritorno. Il nome del metodo è Hello (meglio scriverlo con la maiuscola, come linea guida) e subito dopo troviamo la lista dei parametri, che deve sempre comparire all'interno delle parentesi tonde, come da sintassi formale. Se non vi sono parametri richiesti basta mettere una coppia di parentesi tonde, come avviene spesso nel caso di Main. Il metodo prevede poi il suo codice. Alla riga 14 il metodo viene richiamato col suo nome o col parametro del tipo richiesto. A quel punto viene eseguito il suo codice. Vediamo adesso un esempio di metodo che restituisce un valore:

 
  Esempio 15.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
class program
{
  public static int Somma(int x, int y)
  {
    int somma = x + y;
    return somma;
  }

  public static void Main()
  {
    Console.Write("Inserisci x1: ");
    int x1 = int.Parse(Console.ReadLine());
    Console.Write("Inserisci x2: ");
    int x2 = int.Parse(Console.ReadLine());
    int risultato = Somma(x1, x2);
    Console.WriteLine($"x1 + x2 = {risultato}");
  }
}

Il metodo Somma ha valore di ritorno di tipo int e il valore è restituito al chiamante tramite la keyword return. Tale valore restituito è di fatto un tipo intero a tutti gli effetti e come tale deve sottostare alle regole di attribuzione dei tipi che abbiamo visto; ad esempio se la variabile "risultato" fosse di tipo string il compilatore si lamenterebbe nei soliti modi:

error CS0029: Non è possibile convertire in modo implicito il tipo 'int' in 'string'

Dall'esempio si evince anche, ma dovrebbe essere intuitivo, che i nomi dei parametri passati e quelli nella signature del metodo sono del tutto indipendenti e possono essere scelti a piacimento. I parametri sono passati posizionalmente, ovvero da sinistra a destra, in maniera corrispettiva. Nell'esempio 15.2 quindi x1 si abbina ad x e x2 ad 7.
I parametri fin qui visti sono passati per valore. Questo signfica che viene riversato semplicemente il valore senza alcun legame col parametro di partenza. Si parla di in parameters. Si tratta ovviamente del tipo più semplice. Esiste la possibilità di passare i parametri per riferimento. Questo vuol dire che viene mantenuto un legame tra ciò che viene passato e quello che è del metodo. Entra in scena la keyword ref. Questa stessa parola ci fa pensare ai puntatori, ed in effetti è proprio ciò che avviene, viene passato un puntatore al parametro del chiamante così che ciò che accade all'interno del metodo su un parametro ha effetto sul suo corrispondente all'interno del metodo chiamante. Modifichiamo leggermente, mica tanto :-), l'esempio 15.2:

  Esempio 15.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
class program
{
  public static void Somma(int x, int y, ref int somma)
  {
    somma = x + y;
  }

  public static void Main()
  {
    Console.Write("Inserisci x1: ");
    int x1 = int.Parse(Console.ReadLine());
    Console.Write("Inserisci x2: ");
    int x2 = int.Parse(Console.ReadLine());
    int totale = 0;
    Somma(x1, x2, ref totale);
    Console.WriteLine($"x1 + x2 = {totale}");
  }
}

Ed ecco un output di esempio:

Inserisci x1: 4
Inserisci x2: 6
x1 + x2 = 10

Il funzionamento è presto detto: Il parametro totale, alla riga 16 viene passato per riferimento ed agganciato all'argomento "somma" il quale viene modificato all'interno del metodo. Il suo valore si riversa sulla variabile "totale". Come si vede non c'è nemmeno bisogno di un valore di ritorno.  La variabile che viene passata per riferimento deve essere inizializzata precedentemente. Quest'ultimo è un punto importante e vediamo qui di seguito perchè.

Esiste un terzo metodo di passaggio dei parametri fa uso della keyword out. Il suo funzionamento è del tutto simile a ref, anche in questo caso si tratta di un passaggio per riferimento, ma non è necessario che la variabile passata come parametro sia inizializzata in quanto è obbligatorio che all'interno del metodo sia assegnato un valore all'argomento corrispondente. Usiamo ancora l'esempoio 15.3 modificandolo come segue:

  Esempio 15.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
class program
{
  public static void Somma(int x, int y, out int somma)
  {
    somma = x + y;
  }

  public static void Main()
  {
    Console.Write("Inserisci x1: ");
    int x1 = int.Parse(Console.ReadLine());
    Console.Write("Inserisci x2: ");
    int x2 = int.Parse(Console.ReadLine());
    int totale;
    Somma(x1, x2, out totale);
    Console.WriteLine($"x1 + x2 = {totale}");
  }
}

Il funzionamento è del tutto identico al precedente ma ho evidenziato il fatto che, alla riga 15, la variabile "totale" non è inizializzata, a l contrario di quanto avviene alla riga 15 del programma 15.3. L'obbligo ad inviare un valore per il parametro passato è ovvio se si pensa che questo può essere non inizializzato. Potete provare cosa succede se non restituite nulla verso il chiamante. Va detto che indicando un parametro out l'obbligo di restituire un valore permane anche se inizializaste il parametro che verrà passato. Non ci sono sconti in questo senso. Un'altra grossa differenza operativa è che non si possono usare parametri out per passare dei valori all'interno di un metodo. Vediamo:

  Esempio 15.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
class program
{
  public static void fun(out int y)
  {
    int z = 3 + y;
    Console.WriteLine(z);
    y = 4;
  }

  public static void Main()
  {
    int x = 6;
    fun(out x);
  }
}

Questo programma non compila e il messaggio di errore è lampante:

error CS0269: Uso del parametro out 'y' non assegnato

Se sostituite out con ref inverce le cose funzionano.

L'ultima keyword che potete prendere in considerazione è params. La troverete molto utile ed è usata abbastanza frequentemente all'interno del framework. Ci sono sitazioni in cui non è prevedibile a priori quante saranno i parametri da passare, oppure altre in cui lo stesso metodo viene chiamato in causa in situazioni che necessitano di una differente parametrizzazione, sia per numero che per tipologia. Queste possono essere risolte proprio tramite params che permette di gestire un numero variabile di parametri e, come vedremo, con un piccolo escamotage, anche di gestirne di tipi diversi.

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

  public static int Somma(params int[] valori)
  {
    int temp = 0;
    foreach(int x in valori)
    {
      temp = temp + x;
    }
  return temp;
  }

  public static void Main()
  {
    int[] arr1 = { 1, 2, 3, 4 };
    Console.WriteLine(Somma(arr1));
    int[] arr2 = { 10, 20, 30, 40, 50, 60, 70 };
    Console.WriteLine(Somma(arr2));
  }
}

params, per poter accettare un numero variabile di elementi, è seguito da un array, del quale non è specificata la lunghezza. Si veda la riga 5. Il metodo Somma viene richiamato alla riga 18 ed alla 20 e ad esso viene passato come parametro un array una volta contenente 4 elementi e l'altra 7. Quindi il metodo può essere gestito tramite un numero variabile di parametri in ingresso, come volevasi dimostrare. Oltre che un array era possibile anche semplicemente inviare un elenco di interi separati da virgola, provate ad esempio ad inserire la riga:

Console.WriteLine(Somma(7, 18, 34, 57, 12));

e vedrete che funzionerà senza problemi. Esistono un paio di regole da rispettare:
  • params deve essere l'ultimo della lista dei parametri, non ce ne devono essere altri dopo
  • I parametri devono essere tutti dello stesso tipo

L'ultimo punto è aggirabile come abbiamo già visto per gli array utilizzando un array di oggetti.

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

  public static void StampaTipi(params object[] varitipi)
  {
    foreach(object x in varitipi)
    {
      Console.WriteLine(x.GetType());
    }
  }

  public static void Main()
  {
    StampaTipi(1, "ciao", 1.22, 'a');
  }
}

da cui risulta:

System.Int32
System.String
System.Double
System.Char

Terminata la parte dedicata alle keyword che accompagnano i metodi (in realtà c'è ancora qualcisna, quello che abbiamo visto basta per iniziare a lavorare seriamente) parliamo un attimo di qualche particolarità. Esiste anche una notazione alternativa e più rapide quando il metodo si compone di una sola riga di istruzioni. Si usa l'operatore =>

  Esempio 15.8
1
2
3
4
5
6
7
8
9
10
11
12
using System;
class program
{
  public static void Print(string nome) => Console.WriteLine("ciao " + nome);

  public static void Main()
  {
    Console.Write("Inserisci il tuo nome: ");
    string nome = Console.ReadLine();
    Print(nome);
  }
}

Il metodo Print è tutto riassunto nella riga 4 e normalmente richiamato alla 10.

Anche i parametri hanno la loro particolarità. A partire da Visual Studio 2010 abbiamo i cosiddetti "named parameters" (o parametri con nome o argomenti denominati). Si tratta di parametri individuati tramite il loro nome invece che tramite la posizione. Non è necessario fare grosse acrobazie sintattiche, semplicemente basta richiamare il nome dei parametri, come nell'esempio che segue.

  Esempio 15.9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
class program
{
  public static void Calcoli (int x1, int x2, int x3)
  {
    Console.WriteLine((x1 + x2));
    Console.WriteLine(x3);
  }

  public static void Main()
  {
    Calcoli(1, 2, 3);
    Calcoli(x3: 8, x2: 7, x1: 14);
  }
}

Come si vede in questo caso si prescinde dall'ordine. Il valore assegnato viene preceduto dai due punti. E' anche possibile richiamare con il nome solo uno o una parte dei parametri stando comunque attenti all'ordine, in questi casi, che deve essere comunque conservato. Potete fare qualche test in merito, il procedimento dovrebbe essere molto semplice.

Altro argomento interessante, forse anche di più dei named parameters, anch'essi disponibili a partire dalla versdione di C# che accompagnava  Visual Studio 2010,  sono gli optional parameters (parametri opzionali). Questi sono parametri dotati di un valore di default e quindi possono essere non essere passati dal chiamante nel qual caso useranno tale valore, oppure passati con le modalità consuete con un valore richiesto dal chiamante. La prima regola da rispettare, è che i parametri opzionali, o facoltativi, che dir si voglia, si devono trovare in coda rispetto a tutti quelli obbligatori nella firma del metodo. Inoltre è naturalmednte possibile creare un metodo con tutti i parametri opzionali. I valori assegnati di default ai parametri opzionali possono tuttavia essere sovrascritti in fase di richiamo del metodo.

  Esempio 15.10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
class program
{
  public static void Fun(int x1, int x2, int x3 = 9)
  {
    Console.Write(x1 + " ");
    Console.Write(x2 + " ");
    Console.WriteLine(x3);
  }

  public static void Main()
  {
    Fun(7, 9);
    Fun(7, 9, 10);
  }
}

Il cui ouput è:

7 9 9
7 9 10

Come si vede, la chiamata alla riga 13 viene completata anche se abbiamo passato solo due parametri anzichè i tre presenti nella firma del metodo. Alla riga 14 invece, al metodo Fun vengono passati 3 parametri e il valori di default attribuito a x3 viene sovrascritto dal valore 10. massima flessibilità, in questo senso. Se tutti i parametri sono opzionali gli eventuali valori passati in fase di chiamata del metodo sono assegnati posizionalmente da sinistra a destra.

  Esempio 15.11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
class program
{
  public static void Fun(int x1 = 4, int x2 = 6, int x3 = 9)
  {
    Console.Write(x1 + " ");
    Console.Write(x2 + " ");
    Console.WriteLine(x3);
  }

  public static void Main()
  {
    Fun(77, 97);
    Fun(7, 9, 10);
    Fun(33);
    Fun();
  }
}


Ci sarebbe ancora da parlare dei metodi asincroni, caratterizzati dal modificatore async con await all'interno del corpo, ma si tratta di un argomento interessantissimo ma piuttosto avanzato che coinvolge altri concetti non immediati, per cui sarà meglio affrontarli quando avremo un po' più di esperienza.