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 valore per 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 riferimento per 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 valore per riferimento:
- Se il metodo assegna il parametro per fare riferimento a un oggetto diverso usando
ref =
, 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.
- Se il metodo assegna il parametro per fare riferimento a un oggetto diverso usando
- Quando si passa un tipo riferimento per 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.ref readonly
: 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 parametriin
.
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 modificatoreref
. - Un argomento per un parametro
out
deve includere il modificatoreout
. - Un argomento per un parametro
in
può facoltativamente includere il modificatorein
. Se invece il modificatoreref
viene usato nell'argomento, il compilatore genera un avviso. - Un argomento per un parametro
ref readonly
deve includere i modificatoriin
oref
, 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.
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 è unstruct
o un tipo generico non vincolato a essere uno struct. - Le parole chiave
ref readonly
ein
non possono essere usate a meno che il primo argomento non siastruct
. - Le parole chiave
ref readonly
ein
non possono essere usate in alcun tipo generico, anche se vincolate a uno struct.
Le proprietà non sono variabili. Sono metodi. Le proprietà non possono essere argomenti per i parametri ref
.
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 parametroin
corrispondente. In caso contrario, se due metodi si differenziano solo per la presenza diin
, l'overload per valore rappresenta una corrispondenza migliore. - Specificando
in
, si dichiara l'intenzione di passare un argomento per riferimento. L'argomento usato conin
deve rappresentare una posizione a cui sia possibile fare riferimento direttamente. Sono valide le stesse regole generali diout
eref
: non non è possibile usare costanti, proprietà ordinarie o altre espressioni che producono valori. In caso contrario, l'omissione diin
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 argomentiin
:- 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.
- Una variabile temporanea consente costanti in fase di compilazione come parametri
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
.
Il tipo dichiarato del parametro params
deve essere un tipo di raccolta. I tipi di raccolta riconosciuti sono:
- Un tipo di matrice
T[]
unidimensionale, nel qual caso il tipo di elemento èT
. - Un tipo di intervallo:
System.Span<T>
System.ReadOnlySpan<T>
In questo caso, il tipo di elemento èT
.
- Un tipo con un metodo create accessibile con un tipo di elemento corrispondente. Il metodo create viene identificato usando lo stesso attributo usato per espressioni di raccolta.
- Uno struct o un tipo di classe che implementa System.Collections.Generic.IEnumerable<T> dove:
- Il tipo dispone di un costruttore che può essere richiamato senza argomenti e il costruttore è tanto accessibile quanto il membro dichiarante.
- Il tipo dispone di un metodo di istanza, non un’estensione,
Add
dove:- Il metodo può essere richiamato con un singolo argomento di valore.
- Se il metodo è generico, gli argomenti di tipo possono essere dedotti dall’argomento.
- Il metodo è tanto accessibile quanto il membro dichiarante. In questo caso, il tipo di elemento è il tipo di iterazione del tipo.
- Un tipo di interfaccia:
Prima di C# 13, il parametro deve essere una matrice unidimensionale.
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 i vari modi in cui è possibile inviare argomenti al parametro params
.
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 può 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 altri dettagli, 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#.