TOC

This article is currently in the process of being translated into Italian (~99% done).

Classi:

Method parameters

Nel precedente articolo, abbiamo parlato dei metodi e abbiamo avuto una breve introduzione al concetto di parametri metodo / funzione. In questo articolo, approfondiremo questo argomento in tutte le sue varianti. Come accennato, i metodi possono funzionare senza parametri, ma di solito avranno uno o più parametri, che aiuteranno il metodo a svolgere il suo compito.

Abbiamo già visto uno scenario di utilizzo molto semplice per i parametri nell'articolo precedente: Il nostro metodo AddNumbers (), che accetta due numeri come parametri e restituisce la somma di questi due numeri:

public int AddNumbers(int number1, int number2)
{
	return number1 + number2;
}

Questo mostra molto sucos'è un metodo intelligente, perché con i metodi è possibile incapsulare la funzionalità nel metodo, ma essere in grado di influenzare il risultato quando si chiama questo metodo attraverso i parametri:

AddNumbers(2, 3);

Result: 5

AddNumbers(38, 4);

Result: 42

Questo è il tipo base di parametri, ma parliamo di più di tutti i vari modificatori e opzioni che è possibile utilizzare per modificare il comportamento dei parametri.

Tieni presente che, in questo articolo, analizzeremo approfonditamente tutti i vari tipi di parametri e in che modo essi possono aiutarti; ma se sei un principiante nel C# e vuoi soltanto vedere alcuni semplici risultati, quanto segue potrebbe per ora essere un po' troppo tecnico e complesso. Sentiti libero di saltare il resto dell'articolo e di ritornarci sopra quanto ti sentirai pronto a farlo.

Parametri opzionali

Per impostazione predefinita, quando si chiama un metodo con uno o più parametri, si è obbligati a fornire valori per tutti questi parametri. Tuttavia, in alcune situazioni, potrebbe essere necessario rendere uno o più parametri facoltativi. In alcuni linguaggi di programmazione, è possibile farlo semplicemente contrassegnando il parametro come facoltativo, ma in C #, un parametro viene reso facoltativo fornendo un valore predefinito nella dichiarazione del metodo. Questa è in realtà una buona soluzione, perché ti salva dalla scrittura di codice extra per affrontare situazioni in cui il parametro non viene fornito dal chiamante.

Qui un esempio di un metodo con un parametro opzionale:

public int AddNumbers(int number1, int number2, int number3 = 0)
{
	return number1 + number2 + number3;
}

L'ultimo parametro (numero3) è ora facoltativo, perché è stato fornito un valore predefinito (0). Quando lo chiami, ora puoi fornire due o tre valori, in questo modo:

public void Main(string[] args)
{
	AddNumbers(38, 4);
	AddNumbers(36, 4, 2);
}

Puoi rendere opzionale più di un parametro: in effetti, il tuo metodo può consistere in parametri opzionali solo se necessario. Ricorda solo che i parametri opzionali devono arrivare per ultimi nella dichiarazione del metodo - non prima o tra parametri non opzionali.

Modificatore di parametri

In alternativa, quando devi fornire numerosi parametri facoltativi, puoi usare il modificatore params, il quale permette di fornire un numero arbitrario di parametri. Il risultato potrebbe essere il seguente:

public void GreetPersons(params string[] names) { }

La chiamata potrebbe allora somigliare alla seguente:

GreetPersons("John", "Jane", "Tarzan");

Un altro vantaggio fornito dalla scelta dell'uso di params, è che è possibile in questo caso non passare alcun parametro alla chiamata del metodo. Metodi che usano params possono anche accettare parametri normali, purché quello con il modificatore params sia l'ultimo. Inoltre, nella dichiarazione di un metodo può essere usato soltanto un parametro che usi la parola riservata params. Qui di seguito trovi un esempio completo dove usiamo il modificatore params per visualizzare un numero variabile di nomi con il nostro metodo GreetPersons():

public void Main(string[] args)
{
	GreetPersons("John", "Jane", "Tarzan");
}

public void GreetPersons(params string[] names)
{
	foreach(string name in names)
		Console.WriteLine("Hello " + name);
}

Parameter types: value or reference

Nel C#, come d'altronde in altri linguaggi di programmazione, esiste un differenza tra due tipi di parametro: "per valore" o "per riferimento". Il default in C# è "per valore", che fondamentalmente significa che, quando passi una variabile nella chiamata ad un metodo, in realtà passi una copia di quell'oggetto, e non un riferimento all'oggetto stesso. Ciò significa che è possibile modificare il parametro, all'interno del metodo, senza influenzare l'oggetto originale che hai passato.

Possiamo facilmente dimostrare questo comportamento con un esempio:

public void Main(string[] args)
{
	int number = 20;
	AddFive(number);
	Console.WriteLine(number);
}

public void AddFive(int number)
{
	number = number + 5;
}

Abbiamo qui un metodo molto semplice, chiamato AddFive(), che aggiunge 5 al numero che gli passiamo. Ora, nel nostro metodo Main(), creiamo una variabile che chiamiamo number a cui assegnamo il valore 20, e poi chiamiamo il metodo AddFive(). Alla linea seguente, quando visualizziamo la variabile number, ci potremmo aspettare che il suo valore sia ora 25, ma invece esso è rimasto 20. Come mai? Perché, per default, il parametro fornito al metodo è una copia dell'oggetto originale (cioè, con lo stesso valore), così quando il metodo AddFive() ha operato con il parametro, stava usando una copia, senza influenzare la variabile originale.

Esistono attualmente due modi di cambiare questo funzionamento, così che al nostro metodo AddFive() sia permesso di modificare il valore originale: possiamo usare il modificatore ref oppure i modificatori in/out.

The ref modifier

Il modificatore ref è un'abbreviazione di "reference", che fondamentalmente cambia il comportamento di default del parametro da "per valore" a "per riferimento"; in questo modo, passiamo ora non più una copia del valore della variabile originale, ma il riferimento alla posizione di memoria della variabile stessa. Ecco qui di seguito l'esempio modificato:

public void Main(string[] args)
{
	int number = 20;
	AddFive(ref number);
	Console.WriteLine(number);
}

public void AddFive(ref int number)
{
	number = number + 5;
}

Noterai che ho aggiunto la parola riservata "ref" in due punti: nella dichiarazione del metodo e nella chiamata, dove passo il parametro. Con questa modifica, otteniamo il funzionamento che ci eravamo potuti attendere - il risultato è ora 25 e non 20, perché il modificatore ref consente al metodo di lavorare sull'effettivo valore della variabile, passata come parametro, anziché su una copia di esso.

Il modificatore out

Proprio come il modificatore ref, il modificatore out assicura che il parametro venga passato per riferimento e non per valore, ma con una fondamentale differenza: con il modificatore ref, viene passato un valore inizializzato, che puoi scegliere di modificare all'interno del metodo, oppure di lasciarlo così come si trova. D'altra parte, quando usi il modificatore out, sei obbligato ad inizializzare il parametro all'interno del metodo. Ciò significa anche che puoi passare valori non inizializzati usando il modificatore out - ma il compilatore non consente di uscire da un metodo a cui è stato passato un parametro out senza assegnare un valore a quel parametro.

In C#, un metodo può restituire un solo valore, ma usando il modificatore out, è possibile aggirare questa limitazione passando al metodo numerosi parametri con il modificatore out - una volta chiamato il metodo, a tutti questi parametri dovrà essere assegnato un valore. Qui di seguito un esempio, dove passiamo due numeri e poi, usando modificatori out, restituiamo sia la somma sia la differenza tra i due numeri:

public void Main(string[] args)
{
	int addedValue, subtractedValue;
	DoMath(10, 5, out addedValue, out subtractedValue);
	Console.WriteLine(addedValue);
	Console.WriteLine(subtractedValue);
}

public void DoMath(int number1, int number2, out int addedValue, out int subtractedValue)
{
	addedValue = number1 + number2;
	subtractedValue = number1 - number2;
}
Output:

15
5

Come puoi vedere all'inizio dell'esempio, dichiaro due variabili (addedValue e subtractedValue) prima di passarle come parametri out. Questo era obbligatorio nelle versioni precedenti del linguaggio C#, ma a partire dalla versione 6 del linguaggio, puoi dichiararle direttamente nella chiamata al metodo, così come di seguito:

DoMath(10, 5, out int addedValue, out int subtractedValue);
Console.WriteLine(addedValue);
Console.WriteLine(subtractedValue);

The in modifier

Proprio come il modificatore out, il modificatore in assicura che il parametro venga passato come riferimento invece che come copia del valore della variabile, ma, a differenza del modificatore out, il modificatore in impedisce che venga apportata qualsiasi modifica alla variabile all'interno del metodo.

Il tuo primo pensiero potrebbe essere: se non posso cambiare il valore del parametro, allora tanto vale che lo passi come semplice parametro, dove le modifiche non influiscono sul valore della variabile originale. E avresti ragione, il risultato risulterebbe esattamente lo stesso; ma esiste un'altra ragione molto valida per usare il modificatore in: passando il parametro per riferimento invece che per valore, ottieni un risparmio di risorse poiché il framework non ha bisogno di sprecare tempo per creare la copia dell'oggetto, da passare al metodo come un normale parametro.

Nella maggioranza dei casi, questo non farà grande differenza, perché i parametri sono in gran parte stringhe o numeri; ma in situazioni dove in un loop ripeti molte volte la chiamata allo stesso metodo, o quando passi parametri di grandi dimensioni, come ad esempio stringhe o strutture molto grandi, questo può apportare un gradito miglioramento delle prestazioni.

Qui di seguito ecco un esempio in cui usiamo il modificatore in:

public void Main(string[] args)
{
	string aVeryLargeString = "Lots of text...";
	InMethod(aVeryLargeString);
}

public void InMethod(in string largeString)
{
	Console.WriteLine(largeString);
}

Nel nostro metodo, InMethod(), il parametro largeString è ora un riferimento in sola lettura alla variabile originale (aVeryLargeString), così che possiamo utilizzarla, ma non modificarla. Se ci provassimo, il compilatore genererebbe un errore:

public void InMethod(in string largeString)
{
	largeString = "We can't do this...";
}

Error: Cannot assign to variable 'in string' because it is a readonly variable

Named parameters

Come hai visto in tutti gli esempi precedenti, ogni parametro ha un nome unico nella dichiarazione del metodo. Questo ti consente di riferirti al parametro stesso all'interno del metodo. Tuttavia, quando chiami il metodo, non usi questi nomi - fornisci soltanto i valori, nello stesso ordine in cui sono dichiarati. Questo non è un problema per metodi semplici che accettano due o tre parametri, ma altri metodi sono più complessi, e richiedono un numero più grande di parametri. In quelle situazioni, potrebbe diventare difficile capire a cosa si riferiscono i vari valori nella chiamata ad un metodo, come nell'esempio seguente:

PrintUserDetails(1, "John", 42, null);

Non è molto chiaro il significato dei vari parametri in questa chiamata, ma puoi comprenderlo se osservi la dichiarazione del metodo:

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
}

Dover costantemente controllare le dichiarazioni dei metodi per capire qual è lo scopo dei vari parametri è però fastidioso; così, nei metodi più complessi, puoi fornire i nomi dei parametri direttamente nella chiamata. In questo modo è anche possibile fornire i parametri in qualsiasi ordine, anziché dover rispettare l'ordine di dichiarazione. Ecco qui di seguito un esempio:

PrintUserDetails(name: "John Doe", userId: 1);

Come vantaggio aggiuntivo, questo ti permette di fornire un valore per uno qualsiasi dei tuoi parametri opzionali, senza per forza dover fornire i valori per tutti i parametri opzionali precedenti nella dichiarazione. In altre parole, se vuoi fornire un valore al parametro addressLines in questo esempio, dovresti fornire anche un valore per il parametro age, poiché esso viene prima nella dichiarazione del metodo. Tuttavia, se usi parametri denominati, l'ordine di dichiarazione non ha più importanza e puoi fornire i valori tanto dei parametri obbligatori quanto di uno o più dei parametri opzionali, come nel seguente esempio:

PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);

Qui di seguito ecco un esempio completo sull'uso di parametri denominati:

public void Main(string[] args)
{
	PrintUserDetails(1, "John", 42, null);
	PrintUserDetails(name: "John Doe", userId: 1);
	PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
}

public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
	// Print details...
	Console.WriteLine(name + " is " + age + " years old...");
}

Sommario

Come hai visto in questo articolo, i parametri possono avere diversi tipi e aspetti. Fortunatamente, puoi procedere con i semplici, soliti parametri; ma una volta che inizia a scavare più a fondo nel linguaggio C#, potrai avvalerti con profitto della conoscenza dei tipi e dei modificatori descritti in questo articolo.