Condividi tramite


Parametri e modificatori del metodo

Per impostazione predefinita, C# passa argomenti alle funzioni per valore. Questo approccio passa una copia della variabile al metodo . Per i tipi valore (struct), il metodo ottiene una copia del valore. Per i tipi riferimento (class), il metodo ottiene una copia del riferimento. È possibile usare i modificatori di parametri per passare argomenti per riferimento.

Poiché uno struct è un tipo valore, passando uno struct per valore a un metodo invia una copia dell'argomento al metodo . Il metodo funziona con questa copia. Il metodo non può accedere allo struct originale nel metodo chiamante e non può modificarlo. Il metodo può modificare solo la copia.

Un'istanza di classe è un tipo riferimento, non un tipo valore. Quando si passa un tipo di riferimento per valore a un metodo, il metodo ottiene una copia del riferimento all'istanza di . 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.

Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle versioni di anteprima pubblica per la prossima versione del linguaggio di programmazione.

La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.

Suggerimento

Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.

Passare per valore e passare per riferimento

Tutti gli esempi in questa sezione usano i due record tipi seguenti per illustrare le differenze tra class tipi e struct tipi:

public record struct Point(int X, int Y);
// This doesn't use a primary constructor because the properties implemented for `record` types are 
// readonly in record class types. That would prevent the mutations necessary for this example.
public record class Point3D
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }
}

L'output dell'esempio seguente illustra la differenza tra il passaggio di un tipo struct per valore e il passaggio di un tipo di classe per valore. Entrambi i Mutate metodi modificano i valori delle proprietà dell'argomento. Quando il parametro è un struct tipo, tali modifiche influiscono su una copia dei dati dell'argomento. Quando il parametro è un class tipo, tali modifiche influiscono sull'istanza a cui fa riferimento l'argomento :

public class PassTypesByValue
{
    public static void Mutate(Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }
    public static void Mutate(Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;
        pt.Z = 42;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }

    public static void TestPassTypesByValue()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Mutate(ptStruct);

        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Mutate(ptClass);
        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Mutate:           Point { X = 1, Y = 2 }
        //         Exit Mutate:            Point { X = 19, Y = 23 }
        // After called Mutate:            Point { X = 1, Y = 2 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Mutate:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
        // After called Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
    }
}

Il ref modificatore è un modo per passare argomenti per riferimento ai metodi. Il codice seguente replica l'esempio precedente, ma passa i parametri per riferimento. Le modifiche apportate al struct tipo sono visibili nel metodo chiamante quando lo struct viene passato per riferimento. Non viene apportata alcuna modifica semantica quando viene passato un tipo riferimento per riferimento:

public class PassTypesByReference
{
    public static void Mutate(ref Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }
    public static void Mutate(ref Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;
        pt.Z = 42;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }

    public static void TestPassTypesByReference()
    {
        Console.WriteLine("===== Value Types =====");

        var pStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{pStruct}");

        Mutate(ref pStruct);

        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{pStruct}");

        Console.WriteLine("===== Reference Types =====");

        var pClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{pClass}");

        Mutate(ref pClass);
        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{pClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Mutate:           Point { X = 1, Y = 2 }
        //         Exit Mutate:            Point { X = 19, Y = 23 }
        // After called Mutate:            Point { X = 19, Y = 23 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Mutate:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
        // After called Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
    }
}

Negli esempi precedenti sono state modificate le proprietà di un parametro. Un metodo può anche riassegnare un parametro a un nuovo valore. La riassegnazione si comporta in modo diverso per i tipi di struct e di classe quando vengono passati per valore o per riferimento. Nell'esempio seguente viene illustrato il comportamento dei tipi di struct e dei tipi di classe quando vengono riassegnati i parametri passati per valore:

public class PassByValueReassignment
{
    public static void Reassign(Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point { X = 13, Y = 29 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void Reassign(Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point3D { X = 13, Y = 29, Z = -42 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void TestPassByValueReassignment()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Reassign(ptStruct);

        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Reassign(ptClass);
        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Reassign:         Point { X = 1, Y = 2 }
        //         Exit Reassign:          Point { X = 13, Y = 29 }
        // After called Reassign:          Point { X = 1, Y = 2 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Reassign:         Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
        // After called Reassign:          Point3D { X = 1, Y = 2, Z = 3 }
    }
}

L'esempio precedente mostra che quando si riassegna un parametro a un nuovo valore, tale modifica non è visibile dal metodo chiamante, indipendentemente dal fatto che il tipo sia un tipo valore o un tipo riferimento. Nell'esempio seguente viene illustrato il comportamento quando si riassegna un parametro ricevuto dal metodo per riferimento:

public class PassByReferenceReassignment
{
    public static void Reassign(ref Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point { X = 13, Y = 29 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void Reassign(ref Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point3D { X = 13, Y = 29, Z = -42 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void TestPassByReferenceReassignment()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Reassign(ref ptStruct);

        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Reassign(ref ptClass);
        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Reassign:         Point { X = 1, Y = 2 }
        //         Exit Reassign:          Point { X = 13, Y = 29 }
        // After called Reassign:          Point { X = 13, Y = 29 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Reassign:         Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
        // After called Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
    }
}

Nell'esempio precedente viene illustrato come riassegnare il valore di un parametro passato per riferimento è visibile nel contesto chiamante.

Contesto sicuro di riferimenti e valori

I metodi possono archiviare i valori dei parametri nei campi. Quando si passano parametri per valore, in genere è sicuro. Il metodo copia i valori e i tipi riferimento sono raggiungibili quando il metodo li archivia 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

Per passare argomenti per riferimento anziché per valore, usare uno dei modificatori seguenti in una dichiarazione di parametro:

  • ref: inizializza l'argomento prima di chiamare il metodo . Il metodo può assegnare un nuovo valore al parametro , ma non è necessario.
  • out: il metodo chiamante non deve inizializzare l'argomento prima di chiamare il metodo . Il metodo deve assegnare un valore al parametro.
  • ref readonly: inizializza l'argomento prima di chiamare il metodo . Il metodo non può assegnare un nuovo valore al parametro.
  • in: inizializza l'argomento 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.

Un parametro passato per riferimento è una variabile di riferimento. Non ha un proprio valore. Si riferisce invece a una variabile diversa denominata referenziale. È possibile riassegnare le variabili di riferimento, che ne modificano il referenziale.

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 un membro ha un parametro e l'altro membro ha un refoutparametro , ref readonlyo in . Tuttavia, è possibile eseguire l'overload dei metodi quando un metodo ha un refparametro , ref readonly, ino out e l'altro metodo 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 viene passato per riferimento o tramite una variabile temporanea.

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 membri dell'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.

Le proprietà non sono variabili. Sono metodi. Non è possibile usare le proprietà come argomenti per ref i parametri.

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

È necessario inizializzare un argomento prima di passarlo a un ref parametro.

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;
}

Non è necessario inizializzare le variabili passate come out argomenti prima della chiamata al metodo. Tuttavia, il metodo chiamato deve assegnare un valore prima che venga restituito.

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

La dichiarazione del metodo richiede il ref readonly modificatore. Un modificatore nel sito di chiamata è facoltativo. È possibile usare il in modificatore o ref . Il modificatore ref readonly non è valido nel sito di chiamata. Il 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. La variabile potrebbe essere scrivibile o readonly. 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 ref modificatore 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.

var largeStruct = new LargeStruct { Value1 = 42, Value2 = 3.14, Value3 = "Hello" };

// Using 'in' avoids copying the large struct and prevents modification
ProcessLargeStruct(in largeStruct);
Console.WriteLine($"Original value unchanged: {largeStruct.Value1}");

// Without 'in', the struct would be copied (less efficient for large structs)
ProcessLargeStructByValue(largeStruct);
Console.WriteLine($"Original value still unchanged: {largeStruct.Value1}");

void ProcessLargeStruct(in LargeStruct data)
{
    // Can read the values
    Console.WriteLine($"Processing: {data.Value1}, {data.Value2}, {data.Value3}");
    
    // Uncomment the following line to see error CS8331
    // data.Value1 = 99; // Compilation error: cannot assign to 'in' parameter
}

void ProcessLargeStructByValue(LargeStruct data)
{
    // This method receives a copy of the struct
    Console.WriteLine($"Processing copy: {data.Value1}, {data.Value2}, {data.Value3}");
    
    // Modifying the copy doesn't affect the original
    data.Value1 = 99;
}

Il in modificatore 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.

È possibile ottenere l'ottimizzazione delle prestazioni definendo metodi con in parametri. Alcuni struct argomenti di tipo possono avere dimensioni elevate e, quando si chiamano metodi in cicli ristretti o percorsi di codice critici, il costo della copia di tali strutture è sostanziale. Dichiarare in i parametri per specificare che è possibile passare in modo sicuro gli argomenti per riferimento perché il metodo chiamato non modifica lo stato di tale argomento. 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é int non è più grande di un riferimento nella maggior parte dei computer moderni, non esiste alcun vantaggio per passare un singolo int riferimento come riferimento di sola lettura.

Modificatore params

Il parametro con la params parola chiave deve essere l'ultimo parametro nella dichiarazione del metodo. È possibile usare una params sola parola chiave in una dichiarazione di metodo.

È necessario dichiarare il params parametro come tipo di raccolta. I tipi di raccolta riconosciuti includono:

Prima di C# 13, è necessario usare una matrice unidimensionale per il parametro .

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

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

Nell'esempio seguente vengono illustrati vari modi per inviare argomenti a un params parametro.

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

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

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

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

    // 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 };
    ParamsModifierExample(myIntArray);

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

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

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

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

La risoluzione dell'overload può causare ambiguità quando l'argomento per un params parametro è un tipo di raccolta. Il tipo di raccolta dell'argomento deve essere convertibile nel tipo di raccolta del parametro . Quando overload diversi forniscono conversioni migliori per tale parametro, questo metodo potrebbe essere migliore. Tuttavia, se l'argomento del params parametro è discreto o mancante, tutti gli overload con tipi di parametro diversi params sono uguali per tale parametro.

Per altre informazioni, vedere la sezione relativa agli elenchi di argomenti nella specifica del linguaggio C#. La specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.