Passaggio per riferimento

Passaggio per valore e per riferimento

Documentazione ufficiale ref

Documentazione ufficiale out

Per default, C# passa i parametri per valore: il metodo riceve una copia della variabile originale, quindi le modifiche non influenzano chi ha chiamato il metodo.

void Raddoppia(int n)
{
    n = n * 2;          // modifica solo la copia locale
}

int x = 5;
Raddoppia(x);
Console.WriteLine(x);  // stampa ancora 5

Per permettere a un metodo di modificare la variabile originale si usano le parole chiave ref e out.

ref

ref indica che il parametro e' un alias della variabile originale: leggere o scrivere il parametro dentro il metodo equivale a leggere o scrivere la variabile del chiamante.

Regole fondamentali:

  • La variabile deve essere inizializzata prima di essere passata.
  • La parola chiave ref va scritta sia nel prototipo del metodo che nella chiamata.
void Raddoppia(ref int n)
{
    n = n * 2;          // modifica la variabile originale
}

int x = 5;
Raddoppia(ref x);
Console.WriteLine(x);  // stampa 10

Esempio: scambio di due variabili

Un caso classico in cui ref e' necessario e' la funzione di scambio (swap): senza ref sarebbe impossibile modificare entrambe le variabili originali.

static void Scambia(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10, y = 20;
Scambia(ref x, ref y);
Console.WriteLine($"x = {x}, y = {y}");  // x = 20, y = 10

Esempio: contatore condiviso

ref e' utile quando piu' chiamate successive devono aggiornare la stessa variabile senza usare variabili globali.

static void Incrementa(ref int contatore, int quanto)
{
    contatore += quanto;
}

int totale = 0;
Incrementa(ref totale, 3);
Incrementa(ref totale, 7);
Console.WriteLine(totale);  // 10

out

out serve quando il metodo deve restituire piu' di un valore. A differenza di ref:

  • La variabile non deve essere inizializzata prima della chiamata.
  • Il metodo e' obbligato ad assegnare un valore al parametro out prima di terminare.
static void MinMax(int[] arr, out int min, out int max)
{
    min = arr[0];
    max = arr[0];
    foreach (int val in arr)
    {
        if (val < min) min = val;
        if (val > max) max = val;
    }
}

int[] numeri = { 4, 1, 9, 2, 7 };
int minimo, massimo;                   // non serve inizializzare
MinMax(numeri, out minimo, out massimo);
Console.WriteLine($"Min: {minimo}, Max: {massimo}");  // Min: 1, Max: 9

Esempio: int.TryParse

Il metodo int.TryParse della libreria standard e' un esempio reale e molto comune di out: restituisce true se la conversione ha avuto successo e scrive il risultato nel parametro out.

Console.Write("Inserisci un numero: ");
string testo = Console.ReadLine();
int numero;
if (int.TryParse(testo, out numero))
{
    Console.WriteLine($"Hai inserito il numero {numero}.");
}
else
{
    Console.WriteLine("Input non valido.");
}

ref vs out: confronto

Caratteristica ref out
Variabile deve essere inizializzata Si' No
Il metodo deve assegnare un valore No Si'
Uso tipico Modificare un valore esistente Restituire piu' valori
// ref: x deve gia' avere un valore
int x = 5;
Raddoppia(ref x);

// out: y puo' essere non inizializzata
int y;
Calcola(out y);

Validate