Metodi e Parametri in C#

Dichiarare Metodi con Parametri

Per passare le informazioni necessarie ad un metodo si adopera la lista dei parametri.

Come già menzionato nelle lezioni precedenti, dobbiamo posizionarla tra le parentesi che seguono il nome del metodo, nella dichiarazione:

static <tipo_di_ritorno> <nome_metodo>(<lista_parametri>)
{
// Corpo del metodo
}

La lista dei parametri <lista_parametri> è una lista con zero o più dichiarazioni di variabili, separate da una virgola, in modo che possano essere utilizzate per l'implementazione della logica del metodo:

<lista_parametri> = [<tipo 1> <nome 1>[, <tipo i> <nome i>]],

dove i = 1, 2, 3, \ldots, n

Quando creiamo un metodo e abbiamo bisogno di determinate informazioni in ingresso allo specifico algoritmo, scegliamo quella variabile dalla lista, che è di tipo tipo i e così la utilizziamo con il suo nome nome i.

I parametri della lista possono essere di qualsiasi tipo. Possono essere tipi primitivi (int, double e così via) o tipi di oggetto (ad esempio string o array: int[], double[], string[] e così via).

Esempi

Per rendere più chiaro quanto sopra menzionato, creiamo un metodo che stampa a schermo un saluto. Questo saluto viene personalizzato con il nome di una persona passato come parametro al metodo. Il metodo sarà chiamato StampaSaluto() e avrà un parametro di tipo string chiamato nome:

static void StampaSaluto(string nome)
{
    Console.WriteLine("Ciao, {0}!", nome);
}

Ora, eseguendo il nostro metodo, possiamo visualizzare un saluto rivolto ad una persona qualunque.

Questo è possibile perché abbiamo utilizzato un parametro di tipo string per passare il nome della persona.

L'esempio mostra come utilizzare le informazioni fornite nella lista dei parametri. La variabile nome, che è definita nella lista dei parametri, viene utilizzata nel corpo del metodo con il nome dato nella definizione.

Abbiamo menzionato sopra che, ogni volta che è necessario, possiamo utilizzare array come parametri per un determinato metodo (int[], double[], string[] eccetera). Quindi diamo un'occhiata a un altro esempio per illustrare questo concetto.

Immaginiamo di essere in una libreria e vogliamo calcolare l'importo di denaro che dobbiamo pagare per tutti i libri che abbiamo acquistato.

Creeremo un metodo che ottiene i prezzi di tutti i libri come un array di tipo decimal[], e poi restituisce l'importo totale che dobbiamo pagare:

static void StampaCostoTotale(decimal[] prezzi)
{
    decimal prezzoTotale = 0;

    foreach (decimal prezzoSingolo in prezzi)
    {
        prezzoTotale += prezzoSingolo;
    }

    Console.WriteLine("L'importo totale per tutti i libri è:" + prezzoTotale);
}

Quando un metodo con parametri è dichiarato, il nostro scopo è che ogni volta che invochiamo il metodo, il suo risultato cambi in base al suo input.

In altre parole, l'algoritmo è lo stesso, ma a causa del cambiamento dell'input, il risultato cambia a sua volta. Quando un metodo ha parametri, il suo comportamento dipende tipicamente dai valori dei parametri.

Per chiarire il modo in cui l'esecuzione del metodo dipende dal suo input, diamo un'occhiata a un altro esempio. Il metodo che segue riceve come input un numero di tipo int e, in base a esso, stampa sulla console Positivo, Negativo o Zero:

static void StampaSegno(int numero)
{
    if (numero > 0)
    {
        Console.WriteLine("Positivo");
    }
    else if (numero < 0)
    {
        Console.WriteLine("Negativo");
    }
    else
    {
        Console.WriteLine("Zero");
    }
}

Metodi con Più Parametri

Finora abbiamo visto alcuni esempi di metodi con liste di parametri che consistono di un singolo parametro.

Tuttavia, quando un metodo viene dichiarato, può avere tanti parametri quanti ne necessita.

Se stiamo cercando il massimo tra due valori, ad esempio, il metodo necessita di due parametri:

static void StampaMassimo(float number1, float number2)
{
    float max = number1;
    if (number2 > max)
    {
        max = number2;
    }
    Console.WriteLine("Numero massimo: " + max);
}

Quando viene dichiarato un metodo con più parametri, dobbiamo notare che anche se i parametri sono dello stesso tipo, l'uso della dichiarazione abbreviata delle variabili non è consentito.

Quindi la riga seguente nella dichiarazione del metodo è invalida e produrrà un errore del compilatore:

float var1, var2;

Il tipo dei parametri deve essere esplicitamente scritto prima di ogni parametro, indipendentemente dal fatto che alcuni dei suoi vicini siano dello stesso tipo.

Pertanto, una dichiarazione come quella mostrata di seguito non è valida:

// ERRORE
static void StampaMassimo(float var1, var2)

Il modo corretto di farlo è:

// CORRETTO
static void StampaMassimo(float var1, float var2)

Invocare Metodi con Parametri

L'invocazione di un metodo con uno o più parametri viene eseguita nello stesso modo dell'invocazione di metodi senza parametri.

La differenza è che tra le parentesi che seguono il nome del metodo, inseriamo i valori. Questi valori (chiamati argomenti) verranno assegnati ai parametri appropriati dalla dichiarazione e verranno utilizzati quando il metodo viene eseguito.

Di seguito sono mostrati diversi esempi di metodi con parametri:

StampaSegno(-5);
StampaSegno(balance);
StampaMassimo(100.0f, 200.0f);

Differenza tra Parametri e Argomenti di un Metodo

Prima di continuare lo studio dei metodi, dobbiamo imparare a distinguere tra i nomi dei parametri nella lista dei parametri nella dichiarazione dei metodi e i valori che passiamo quando invochiamo un metodo.

Per chiarire, quando dichiariamo un metodo, qualsiasi elemento della lista dei parametri lo chiameremo Parametro (in altri testi vengono spesso chiamati parametri formali).

Quando invochiamo un metodo, i valori che usiamo per assegnare ai suoi parametri sono chiamati Argomenti.

In altre parole, gli elementi nella lista dei parametri (var1 e var2) sono chiamati parametri:

static void StampaMassimo(float var1, float var2)

Di conseguenza, i valori per l'invocazione del metodo (-23.5 e 100) sono chiamati argomenti:

PrintMax(100.0f, -23.5f);

Passare Argomenti di Tipo Primitivo

Come appena spiegato, in C# quando una variabile viene passata come argomento di un metodo, il suo valore viene copiato nel parametro dalla dichiarazione del metodo. Dopo di ciò, la copia verrà utilizzata nel corpo del metodo.

Tuttavia, c'è una cosa di cui dovremmo essere consapevoli. Se il parametro dichiarato è di tipo primitivo, l'uso degli argomenti non cambia l'argomento stesso, cioè il valore dell'argomento non cambierà per il codice dopo che il metodo è stato invocato.

Quindi, se abbiamo un pezzo di codice come quello sotto:

static void StampaNumero(int parametroNumero)
{
    // Modifica del parametro di tipo primitivo
    parametroNumero = 5;
    Console.WriteLine("nel metodo StampaNumero(), dopo la " +
        "modifica, parametroNumero è: {0}", parametroNumero);
}

Invocazione del metodo da Main():

static void Main()
{
    int argomentoNumero = 3;
    // Copia del valore 3 dell'argomento argomentoNumero nel
    // parametro parametroNumero
    PrintNumber(argomentoNumero);
    Console.WriteLine("nel metodo Main() argomentoNumero è: " +
        argomentoNumero);
}

Il valore 3 di argomentoNumero viene copiato nel parametro parametroNumero. Dopo che il metodo StampaNumero() è stato invocato, a parametroNumero viene assegnato il valore 5.

Questo non influisce sul valore della variabile argomentoNumero, perché invocando quel metodo, la variabile parametroNumero mantiene una copia del valore dell'argomento.

Ecco perché il metodo StampaNumero() stampa il numero 5. Pertanto, dopo l'invocazione del metodo StampaNumero() nel metodo Main() ciò che viene stampato è il valore di argomentoNumero e come si può vedere quel valore non è cambiato. Il risultato della riga sopra è stampato di seguito:

nel metodo StampaNumero(), dopo la modifica, parametroNumero è: 5
nel metodo Main() argomentoNumero è: 3

Passare Argomenti di Tipo Riferimento

Quando dobbiamo dichiarare (e quindi invocare) un metodo, che ha parametri di tipo riferimento (come array), dobbiamo prestare attenzione.

Prima di spiegare il motivo di questa considerazione, dobbiamo ricordarci qualcosa dalle lezioni sugli array. Un array, come qualsiasi altro tipo riferimento, consiste di una variabile-puntatore (riferimento all'oggetto) e un valore, ossia le informazioni reali conservate nella memoria del computer (che chiamiamo oggetto).

Nel nostro caso l'oggetto è il vero array di elementi. L'indirizzo di questo oggetto, tuttavia, è conservato nella variabile (cioè l'indirizzo dove gli elementi dell'array sono posizionati nella memoria):

Variabile di tipo Riferimento
Figura 1: Variabile di tipo Riferimento

Quindi ogni volta che operiamo con array in C#, li accediamo sempre tramite quella variabile (l'indirizzo o puntatore o riferimento) che abbiamo usato per dichiarare il particolare array.

Questo è il principio base per qualsiasi altro tipo riferimento. Pertanto, ogni volta che un argomento di tipo riferimento viene passato a un metodo, il parametro del metodo riceve il riferimento stesso. Ma cosa succede con l'oggetto allora (il vero array)? Viene anche copiato o no?

Per spiegare questo, facciamo il seguente esempio: supponiamo di avere un metodo ModificaArray(), che modifica il primo elemento di un array passato come parametro, quindi reinizializza il primo elemento con valore 5 e poi stampa gli elementi dell'array, circondati da parentesi quadre e separati da virgole:

static void ModificaArray(int[] parametroArray)
{
    parametroArray[0] = 5;
    Console.Write("In ModificaArray() il parametro è: ");
    StampaArray(parametroArray);
}

static void StampaArray(int[] parametroArray)
{
    Console.Write("[");
    int length = parametroArray.Length;
    if (length > 0)
    {
        Console.Write(parametroArray[0].ToString());
        for (int i = 1; i < length; i++)
        {
            Console.Write(", {0}", parametroArray[i]);
        }
    }
    Console.WriteLine("]");
}

Dichiariamo anche il metodo Main(), dal quale invochiamo il nuovo metodo creato ModificaArray():

static void Main()
{
    int[] argomentoArray = new int[] { 1, 2, 3 };
    Console.Write("Prima di ModificaArray() l'argomento è: ");
    StampaArray(argomentoArray);
    // Modifica dell'argomento dell'array
    ModificaArray(argomentoArray);
    Console.Write("Dopo ModificaArray() l'argomento è: ");
    StampaArray(argomentoArray);
}

Quale sarebbe il risultato dell'esecuzione del codice? Diamo un'occhiata:

Prima di ModificaArray() l'argomento è: [1, 2, 3]
In ModificaArray() il parametro è: [5, 2, 3]
Dopo ModificaArray() l'argomento è: [5, 2, 3]

È evidente che dopo l'esecuzione del metodo ModificaArray(), l'array a cui si riferisce la variabile argomentoArray non consiste più di [1,2,3], ma di [5,2,3] invece. Cosa significa questo?

La ragione di tale risultato sta nel fatto che passando argomenti di tipo riferimento, viene copiata solo la variabile che mantiene l'indirizzo dell'oggetto. Si noti che questo non copia l'oggetto stesso.

Definizione

Passaggio di Argomenti di Tipo Riferimento

Quando un argomento di tipo riferimento viene passato a un metodo, il parametro del metodo riceve il riferimento stesso. Questo significa che il parametro del metodo punta allo stesso oggetto a cui punta l'argomento.

Pertanto qualsiasi modifica fatta all'oggetto, a cui punta il parametro del metodo, influenzerà l'oggetto originale, a cui punta l'argomento.

Cerchiamo di illustrare quanto appena spiegato. Utilizzeremo alcune figure per l'esempio che abbiamo usato sopra.

Invocando il metodo ModificaArray(), il valore del parametro parametroArray non è definito e non mantiene un riferimento a nessun oggetto particolare (non un vero array):

Argomento e Parametro prima della chiamata al Metodo
Figura 2: Argomento e Parametro prima della chiamata al Metodo

Al momento dell'invocazione di ModificaArray(), il valore mantenuto nell'argomento argomentoArray viene copiato nel parametro parametroArray:

Copia dell'argomento nel parametro
Figura 3: Copia dell'argomento nel parametro

In questo modo, copiando il riferimento agli elementi dell'array nella memoria dall'argomento al parametro, diciamo al parametro di puntare allo stesso oggetto a cui punta l'argomento:

Argomento e Parametro puntano allo stesso oggetto
Figura 4: Argomento e Parametro puntano allo stesso oggetto

Questo è il punto a cui dobbiamo prestare attenzione. Se il metodo invocato modifica l'oggetto, a cui viene passato un riferimento, ciò può influenzare l'esecuzione del codice dopo l'invocazione del metodo (come abbiamo visto nell'esempio, il metodo StampaArray() non stampa l'array che era stato inizialmente passato).

La differenza tra trattare con argomenti di tipo primitivo e di tipo riferimento sta nel modo in cui vengono passati: i tipi primitivi vengono passati per valore, mentre gli oggetti vengono passati per riferimento.

Passaggio di Espressioni come Argomenti di Metodi

Quando un metodo viene invocato, possiamo passare un'intera espressione invece di singoli argomenti.

Facendo così, il C# calcola i valori per quelle espressioni e al momento dell'esecuzione del codice (se possibile, questo viene fatto in fase di compilazione) sostituisce l'espressione con il suo risultato, quando il metodo viene invocato.

Il seguente codice mostra l'invocazione di metodi, passando espressioni come argomenti di metodi:

StampaSegno(2 + 3);

float vecchiaQuantita = 3;
float quantita = 2;

StampaMassimo(vecchiaQuantita * 5, quantita * 2);

Il risultato dell'esecuzione di quei metodi è:

Positivo
Numero massimo: 15.0

Quando un metodo con parametri viene invocato, dobbiamo essere consapevoli di alcune regole specifiche, che verranno spiegate nelle prossime sottosezioni.

Passaggio di Argomenti Compatibili con il Tipo di Parametro

Dobbiamo sapere che possiamo passare solo argomenti che sono di tipo compatibile con il parametro correlato, dichiarato nella lista dei parametri del metodo.

Ad esempio, se il parametro che il metodo si aspetta nella sua dichiarazione è di tipo float, invocando il metodo possiamo passare un valore di tipo int. Verrà convertito dal compilatore in un valore di tipo float e poi verrà passato al metodo per la sua esecuzione:

static void StampaNumero(float numero)
{
    Console.WriteLine("Il numero float è: {0}", numero);
}

static void Main()
{
    // Invochiamo il metodo passando un int
    StampaNumero(5);
}

Nell'esempio, invocando StampaNumero() nel metodo Main(), prima il valore letterale intero 5 (che implicitamente è di tipo int) viene convertito nel valore a virgola mobile correlato 5.0f.

Poi il valore così convertito viene passato al metodo StampaNumero(). Come ci si aspetta, il risultato di quell'esecuzione del codice è:

Il numero float è: 5.0

Compatibilità del Parametro del Metodo e del Valore Passato

Il risultato del calcolo di un'espressione, passato come argomento, deve essere dello stesso tipo del tipo del parametro dichiarato, o compatibile con quel tipo come abbiamo detto sopra.

Quindi, se è richiesto un parametro di tipo float, possiamo passare il valore calcolato da un'espressione che è di tipo int.

Ad esempio, ritornando all'esempio sopra, se invece di StampaNumero(5), chiamassimo il metodo, con 5 sostituito dall'espressione 2+3, il risultato del calcolo di quell'espressione deve essere di tipo float (quello che il metodo si aspetta), o di un tipo che può essere convertito in float senza perdita (nel nostro caso questo è int).

Quindi modifichiamo un po' il metodo Main() dal passaggio sopra, per illustrare quanto appena spiegato:

static void Main()
{
    StampaNumero(2 + 3);
}

In questo esempio prima verrà eseguita la somma. Poi il risultato intero 5 verrà convertito nel suo equivalente a virgola mobile 5.0f. Fatto questo, il metodo StampaNumero() verrà invocato con l'argomento 5.0f. Il risultato sarà di nuovo:

Il numero float è: 5.0

Mantenere la Sequenza di Dichiarazione dei Tipi di Argomenti

I valori, che vengono passati al metodo, al momento della sua invocazione, devono comparire nello stesso ordine in cui i parametri sono dichiarati nella lista dei parametri.

Questo è dovuto alla firma del metodo, che abbiamo studiato in precedenza.

Per chiarire, discutiamo il seguente esempio: abbiamo un metodo StampaNomeEEta(), nella cui dichiarazione del metodo c'è una lista di parametri, con parametri di tipo string e int, ordinati come mostrato di seguito:

static void StampaNomeEEta(string nome, int eta)
{
    Console.WriteLine("Sono {0}, ho {1} anno/i.", nome, eta);
}

Aggiungiamo un metodo Main(), in quel metodo invocheremo il metodo StampaNomeEEta():

static void Main()
{
    // Sequenza corretta di argomenti
    StampaNomeEEta("Mario", 25);
}

In questo modo, il metodo viene invocato correttamente e il risultato è il seguente:

Sono Mario, ho 25 anno/i.

Proviamo, invece, a passare i parametri in ordine inverso (come tipi), quindi invece di "Mario" e 25, usiamo 25 e "Mario":

static void Main()
{
    // Sequenza errata di argomenti
    StampaNomeEEta(25, "Mario");
}

Il compilatore in questo caso non sarà in grado di trovare un metodo chiamato StampaNomeEEta, che accetta parametri nella sequenza int e string. Per questo motivo, il compilatore notificherà un errore:

error CS1503: Argument 1: cannot convert from 'int' to 'string'
error CS1503: Argument 1: cannot convert from 'int' to 'string'

Il compilatore ci sta dicendo che non può convertire 25 in string e, analogamente, "Mario" in int.

In Sintesi

In questa lezione abbiamo approfondito i concetti e i meccanismi per dichiarare e invocare metodi con parametri in C#. In particolare, abbiamo imparato che:

  • Un metodo può avere zero o più parametri;
  • I parametri sono dichiarati nella lista dei parametri del metodo;
  • I parametri possono essere di qualsiasi tipo, primitivo o di oggetto;
  • Gli argomenti passati al metodo devono essere compatibili con i tipi dei parametri;
  • Gli argomenti devono essere passati nella stessa sequenza in cui i parametri sono dichiarati.

Abbiamo, per il momento, studiato il caso in cui la lista di parametri è fissa. Tuttavia, il C# ci permette di dichiarare metodi con un numero variabile di argomenti. Questo argomento sarà trattato nella prossima lezione.