Parametri dei metodi

Per impostazione predefinita, gli argomenti in C# vengono passati alle funzioni per valore. Ciò significa che viene passata una copia della variabile al metodo. Per i tipi valore (struct), una copia del valore viene passata al metodo. Per i tipi riferimento (class), viene passata una copia del riferimento al metodo. I modificatori di parametri consentono di passare argomenti per riferimento. I concetti seguenti consentono di comprendere queste distinzioni e come usare i modificatori di parametri:

  • Passaggio per valore significa passare una copia della variabile al metodo.
  • Passaggio per riferimento significa passare l'accesso alla variabile al metodo.
  • Una variabile di un tipo riferimento contiene un riferimento ai relativi dati.
  • Una variabile di un tipo valore contiene direttamente i dati.

Poiché uno struct è un tipo valore, il metodo riceve una copia dell'argomento dello struct, su cui opera. Il metodo non ha accesso allo struct originale nella chiamata e quindi non può modificarlo in alcun modo. Il metodo può modificare solo la copia.

Un'istanza di classe è un tipo riferimento, non un tipo valore. Quando si passa un tipo riferimento in base al valore a un metodo, il metodo riceve una copia del riferimento all'istanza di classe. Entrambe le variabili fanno riferimento allo stesso oggetto. Il parametro è una copia del riferimento. Il metodo chiamato non può riassegnare l'istanza nel metodo chiamante. Tuttavia, il metodo chiamato può usare la copia del riferimento per accedere ai membri dell'istanza. Se il metodo chiamato modifica un membro dell'istanza, il metodo chiamante visualizza anche tale modifica perché fa riferimento alla stessa istanza.

L'output dell'esempio seguente illustra la differenza. Il metodo ClassTaker modifica il valore del campo willIChange perché il metodo usa l'indirizzo nel parametro per trovare il campo specificato dell'istanza della classe. Il campo willIChange dello struct nel metodo chiamante non cambia chiamando StructTaker perché il valore dell'argomento è una copia dello struct, non una copia del relativo indirizzo. StructTaker modifica la copia e la copia viene persa quando la chiamata a StructTaker viene completata.

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

Combinazioni di tipo parametro e modalità argomento

La modalità di passaggio di un argomento, e se si tratta di un tipo riferimento o di un tipo valore, controlla quali modifiche apportate all'argomento sono visibili dal chiamante:

  • Quando si passa un tipo valoreper valore:
    • Se il metodo assegna il parametro per fare riferimento a un oggetto diverso, tali modifiche non sono visibili dal chiamante.
    • Se il metodo modifica lo stato dell'oggetto a cui fa riferimento il parametro, tali modifiche non sono visibili dal chiamante.
  • Quando si passa un tipo riferimentoper valore:
    • Se il metodo assegna il parametro per fare riferimento a un oggetto diverso, tali modifiche non sono visibili dal chiamante.
    • Se il metodo modifica lo stato dell'oggetto a cui fa riferimento il parametro, tali modifiche sono visibili dal chiamante.
  • Quando si passa un tipo valoreper riferimento:
    • Se il metodo assegna il parametro per fare riferimento a un oggetto diverso, tali modifiche non sono visibili dal chiamante.
    • Se il metodo modifica lo stato dell'oggetto a cui fa riferimento il parametro, tali modifiche sono visibili dal chiamante.
  • Quando si passa un tipo riferimentoper riferimento:
    • Se il metodo assegna il parametro per fare riferimento a un oggetto diverso, tali modifiche sono visibili dal chiamante.
    • Se il metodo modifica lo stato dell'oggetto a cui fa riferimento il parametro, tali modifiche sono visibili dal chiamante.

Il passaggio di un tipo riferimento per riferimento consente al metodo chiamato di sostituire l'oggetto a cui fa riferimento il parametro per riferimento nel chiamante. Il percorso di archiviazione dell'oggetto viene passato al metodo come valore del parametro referenziato. Se si modifica il valore nella posizione di archiviazione del parametro (in modo che punti a un nuovo oggetto), è anche possibile modificare il percorso di archiviazione a cui fa riferimento il chiamante. Nell'esempio seguente viene passata un'istanza di un tipo di riferimento come parametro ref.

class Product
{
    public Product(string name, int newID)
    {
        ItemName = name;
        ItemID = newID;
    }

    public string ItemName { get; set; }
    public int ItemID { get; set; }
}

private static void ChangeByReference(ref Product itemRef)
{
    // Change the address that is stored in the itemRef parameter.
    itemRef = new Product("Stapler", 12345);
}

private static void ModifyProductsByReference()
{
    // Declare an instance of Product and display its initial values.
    Product item = new Product("Fasteners", 54321);
    System.Console.WriteLine("Original values in Main.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);

    // Pass the product instance to ChangeByReference.
    ChangeByReference(ref item);
    System.Console.WriteLine("Calling method.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);
}

// This method displays the following output:
// Original values in Main.  Name: Fasteners, ID: 54321
// Calling method.  Name: Stapler, ID: 12345

Contesto sicuro di riferimenti e valori

I metodi possono archiviare i valori dei parametri nei campi. Quando i parametri vengono passati per valore, in genere è sicuro. I valori vengono copiati e i tipi riferimento sono raggiungibili quando vengono archiviati in un campo. Il passaggio sicuro dei parametri per riferimento richiede al compilatore di definire quando è sicuro assegnare un riferimento a una nuova variabile. Per ogni espressione, il compilatore definisce un contesto sicuro che delimita l'accesso a un'espressione o a una variabile. Il compilatore usa due ambiti: safe-context e ref-safe-context.

  • Safe-context definisce l'ambito in cui è possibile accedere a qualsiasi espressione in modo sicuro.
  • Ref-safe-context definisce l'ambito in cui è possibile accedere o modificare in modo sicuro un riferimento a qualsiasi espressione.

In modo informale, è possibile considerare questi ambiti come il meccanismo per garantire che il codice modifichi o acceda mai a un riferimento non più valido. Un riferimento è valido purché faccia riferimento a un oggetto o a uno struct valido. Safe-context definisce quando è possibile assegnare o riassegnare una variabile. Ref-safe-context definisce quando una variabile può essere assegnata ref o riassegnata ref. L'assegnazione assegna una variabile a un nuovo valore; assegnazione ref assegna la variabile per fare riferimento a un percorso di archiviazione diverso.

Parametri di riferimento

Si applica uno dei modificatori seguenti a una dichiarazione di parametro per passare argomenti per riferimento anziché per valore:

  • ref: l'argomento deve essere inizializzato prima di chiamare il metodo. Il metodo può assegnare un nuovo valore al parametro, ma non è una procedura necessaria.
  • out: il metodo chiamante non è necessario per inizializzare l'argomento prima di chiamare il metodo. Il metodo deve assegnare un valore al parametro.
  • readonly ref: l'argomento deve essere inizializzato prima di chiamare il metodo. Il metodo non può assegnare un nuovo valore al parametro.
  • in: l'argomento deve essere inizializzato prima di chiamare il metodo. Il metodo non può assegnare un nuovo valore al parametro. Il compilatore potrebbe creare una variabile temporanea per contenere una copia dell'argomento dei parametri in.

I membri di una classe non possono avere firme che differiscono solo per ref, ref readonly, in o out. Un errore del compilatore si verifica se l'unica differenza tra due membri di un tipo è che uno di essi ha un parametro ref e l'altro ha un parametro out, ref readonly o in. È tuttavia possibile eseguire l'overload dei metodi quando un metodo ha un parametro ref, ref readonly, in o out e l'altro ha un parametro passato per valore, come illustrato nell'esempio seguente. In altre situazioni che richiedono la firma corrispondente, ad esempio nascondere o sottoporre a override, in, ref, ref readonly e out fanno parte della firma e non sono corrispondenti tra loro.

Quando un parametro ha uno dei modificatori precedenti, l'argomento corrispondente può avere un modificatore compatibile:

  • Un argomento per un parametro ref deve includere il modificatore ref.
  • Un argomento per un parametro out deve includere il modificatore out.
  • Un argomento per un parametro in può facoltativamente includere il modificatore in. Se invece il modificatore ref viene usato nell'argomento, il compilatore genera un avviso.
  • Un argomento per un parametro ref readonly deve includere i modificatori in o ref, ma non entrambi. Se non è incluso alcun modificatore, il compilatore genera un avviso.

Quando si usano questi modificatori, descrivono come viene usato l'argomento:

  • ref indica che il metodo può leggere o scrivere il valore dell'argomento.
  • out indica che il metodo imposta il valore dell'argomento.
  • ref readonly indica che il metodo legge, ma non può scrivere il valore dell'argomento. L'argomento deve essere passato per riferimento.
  • in indica che il metodo legge, ma non può scrivere il valore dell'argomento. L'argomento verrà passato per riferimento o tramite una variabile temporanea.

Le proprietà non sono variabili. Sono metodi e non possono essere passati ai parametri ref. Non è possibile usare i modificatori di parametri precedenti nei tipi di metodi seguenti:

  • Metodi asincroni definiti usando il modificatore async.
  • Metodi Iterator, che comprendono un'istruzione yield return o yield break.

I metodi di estensione hanno anche restrizioni sull'uso di queste parole chiave di argomento:

  • La parola chiave out non può essere usata nel primo argomento di un metodo di estensione.
  • La parola chiave ref non può essere usata nel primo argomento di un metodo di estensione quando l'argomento non è un struct o un tipo generico non vincolato a essere uno struct.
  • Le parole chiave ref readonly e in non possono essere usate a meno che il primo argomento non sia struct.
  • Le parole chiave ref readonly e in non possono essere usate in alcun tipo generico, anche se vincolate a uno struct.

Modificatore di parametri ref

Per usare un parametro ref, la definizione del metodo e il metodo chiamante devono usare in modo esplicito la parola chiave ref, come illustrato nell'esempio seguente. (Ad eccezione del fatto che il metodo chiamante può omettere ref quando si effettua una chiamata COM.)

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45

Un argomento passato a un parametro ref deve essere inizializzato prima di essere passato.

Modificatore di parametri out

Per usare un parametro out, la definizione del metodo e il metodo chiamante devono usare in modo esplicito la parola chiave out. Ad esempio:

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     // value is now 44

void OutArgExample(out int number)
{
    number = 44;
}

Le variabili passate come argomenti out non devono essere inizializzate prima di essere passate in una chiamata al metodo. È necessario tuttavia che il metodo chiamato assegni un valore prima della restituzione del metodo.

I metodi di decostruzione dichiarano i relativi parametri con il modificatore out per restituire più valori. Altri metodi possono restituire tuple di valori per più valori restituiti.

È possibile dichiarare una variabile in un'istruzione separata prima di passarla come argomento out. È anche possibile dichiarare la variabile out nell'elenco di argomenti della chiamata al metodo, anziché in una dichiarazione di variabile separata. Le dichiarazioni di variabile out producono codici più compatti e leggibili e impediscono l'assegnazione accidentale di un valore alla variabile prima della chiamata al metodo. Nell'esempio seguente viene definita la variabile number nella chiamata al metodo Int32.TryParse.

string numberAsString = "1640";

if (Int32.TryParse(numberAsString, out int number))
    Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
    Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
//       Converted '1640' to 1640

È anche possibile dichiarare una variabile locale tipizzata in modo implicito.

Modificatore ref readonly

Il modificatore ref readonly deve essere presente nella dichiarazione del metodo. Un modificatore nel sito di chiamata è facoltativo. È possibile usare il modificatorein o ref. Il modificatore ref readonly non è valido nel sito di chiamata. Quale modificatore usato nel sito di chiamata può aiutare a descrivere le caratteristiche dell'argomento. È possibile usare ref solo se l'argomento è una variabile ed è scrivibile. È possibile usare in solo quando l'argomento è una variabile. Potrebbe essere scrivibile o di sola lettura. Non è possibile aggiungere alcun modificatore se l'argomento non è una variabile, ma è un'espressione. Gli esempi seguenti mostrano queste condizioni. Il metodo seguente usa il modificatore ref readonly per indicare che uno struct di grandi dimensioni deve essere passato per riferimento per motivi di prestazioni:

public static void ForceByRef(ref readonly OptionStruct thing)
{
    // elided
}

È possibile chiamare il metodo usando il modificatore ref o in. Se si omette il modificatore, il compilatore genera un avviso. Quando l'argomento è un'espressione, non una variabile, non è possibile aggiungere i modificatori in o ref, pertanto è consigliabile eliminare l'avviso:

ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference

Se la variabile è una variabile readonly, è necessario usare il modificatore in. Se invece si usa il modificatore ref, il compilatore genera un errore.

Il modificatore ref readonly indica che il metodo prevede che l'argomento sia una variabile anziché un'espressione che non è una variabile. Esempi di espressioni che non sono variabili sono costanti, valori restituiti dal metodo e proprietà. Se l'argomento non è una variabile, il compilatore genera un avviso.

Modificatore di parametri in

Il modificatore in è necessario nella dichiarazione del metodo, ma non nel sito di chiamata.

int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument);     // value is still 44

void InArgExample(in int number)
{
    // Uncomment the following line to see error CS8331
    //number = 19;
}

Il modificatore in consente al compilatore di creare una variabile temporanea per l'argomento e di passare un riferimento di sola lettura a tale argomento. Il compilatore crea sempre una variabile temporanea quando l'argomento deve essere convertito, quando è presente una conversione implicita dal tipo di argomento o quando l'argomento è un valore che non è una variabile. Ad esempio, quando l'argomento è un valore letterale o il valore restituito da una funzione di accesso a una proprietà. Quando l'API richiede che l'argomento venga passato per riferimento, scegliere il modificatore ref readonly anziché il modificatore in.

I metodi definiti usando parametri in possono potenzialmente ottenere l'ottimizzazione delle prestazioni. Alcuni argomenti di tipo struct possono essere di grandi dimensioni e quando vengono chiamati metodi all'interno di cicli ristretti o in percorsi di codice critici, il costo della copia di tali strutture ha una rilevanza fondamentale. I metodi dichiarano parametri in per specificare che è possibile passare argomenti per riferimento in modo sicuro, perché il metodo chiamato non modifica lo stato degli argomenti. Il passaggio di tali argomenti per riferimento consente di evitare una copia potenzialmente dispendiosa. Si aggiunge il modificatore in in modo esplicito presso il sito di chiamata per assicurarsi che l'argomento venga passato per riferimento, non per valore. L'uso di in in modo esplicito ha i due effetti seguenti:

  • Se si specifica in presso il sito di chiamata si impone al compilatore di selezionare un metodo definito con un parametro in corrispondente. In caso contrario, se due metodi si differenziano solo per la presenza di in, l'overload per valore rappresenta una corrispondenza migliore.
  • Specificando in, si dichiara l'intenzione di passare un argomento per riferimento. L'argomento usato con in deve rappresentare una posizione a cui sia possibile fare riferimento direttamente. Sono valide le stesse regole generali di out e ref: non non è possibile usare costanti, proprietà ordinarie o altre espressioni che producono valori. In caso contrario, l'omissione di in presso il sito di chiamata informa il compilatore che è consentito creare una variabile temporanea da passare per riferimento di sola lettura al metodo. Il compilatore crea una variabile temporanea per superare diverse restrizioni degli argomenti in:
    • Una variabile temporanea consente costanti in fase di compilazione come parametri in.
    • Una variabile temporanea consente proprietà o altre espressioni per i parametri in.
    • Una variabile temporanea consente argomenti che includono una conversione implicita dal tipo di argomento al tipo di parametro.

In tutte le istanze precedenti, il compilatore crea una variabile temporanea che archivia il valore della costante, della proprietà o di un'altra espressione.

Il codice seguente illustra queste regole:

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`

Si supponga a questo punto che sia disponibile un altro metodo che usa argomenti per valore. I risultati cambiano, come illustrato nel codice seguente:

static void Method(int argument)
{
    // implementation removed
}

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`

L'unica chiamata a un metodo in cui l'argomento viene passato per riferimento è quella finale.

Nota

Per semplicità, il codice precedente usa int come tipo di argomento. Poiché nella maggior parte dei computer moderni le dimensioni di int non sono maggiori di quelle di un riferimento, non si ottiene alcun vantaggio dal passaggio di un unico int come riferimento di sola lettura.

Modificatore params

In una dichiarazione di metodo non è possibile aggiungere altri parametri dopo la parola chiave params ed è consentito l'uso di una sola parola chiave params.

Se il tipo dichiarato del parametro params non è una matrice unidimensionale, si verifica l'errore CS0225 del compilatore.

Quando si chiama un metodo con un parametro params, è possibile passare:

  • Un elenco delimitato da virgole di argomenti del tipo degli elementi della matrice.
  • Una matrice di argomenti del tipo specificato.
  • Nessun argomento. Se non vengono inviati argomenti, la lunghezza dell'elenco params è zero.

Nell'esempio seguente vengono illustrati i vari modi in cui è possibile inviare argomenti al parametro params.

public class MyClass
{
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    public static void UseParams2(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        // You can send a comma-separated list of arguments of the
        // specified type.
        UseParams(1, 2, 3, 4);
        UseParams2(1, 'a', "test");

        // A params parameter accepts zero or more arguments.
        // The following calling statement displays only a blank line.
        UseParams2();

        // An array argument can be passed, as long as the array
        // type matches the parameter type of the method being called.
        int[] myIntArray = { 5, 6, 7, 8, 9 };
        UseParams(myIntArray);

        object[] myObjArray = { 2, 'b', "test", "again" };
        UseParams2(myObjArray);

        // The following call causes a compiler error because the object
        // array cannot be converted into an integer array.
        //UseParams(myObjArray);

        // The following call does not cause an error, but the entire
        // integer array becomes the first element of the params array.
        UseParams2(myIntArray);
    }
}
/*
Output:
    1 2 3 4
    1 a test

    5 6 7 8 9
    2 b test again
    System.Int32[]
*/