Paramètres de méthodes
Par défaut, les arguments en C# sont passés aux fonctions par valeur. Cela signifie qu’une copie de la variable est passée à la méthode. Pour les types valeur (struct
), une copie de la valeur est passée à la méthode. Pour les types référence (class
), une copie de la référence est passée à la méthode. Les modificateurs de paramètres vous permettent de passer des arguments par référence. Les concepts suivants vous aident à comprendre ces distinctions et à utiliser les modificateurs de paramètres :
- Passer par valeur signifie passer une copie de la variable à la méthode.
- Passer par référence signifie passer l’accès à la variable à la méthode.
- Une variable d’un type référence contient une référence à ses données.
- Une variable d’un type valeur contient ses données directement.
Étant donné qu’un struct est un type valeur, la méthode reçoit et traite une copie de l’argument struct quand vous passez un struct par valeur à une méthode. La méthode n’a pas accès au struct d’origine dans la méthode d’appel, et ne peut donc pas le modifier de quelque manière que ce soit. La méthode peut modifier uniquement la copie.
Une instance de classe est un type référence, pas un type valeur. Quand un type référence est passé par valeur à une méthode, celle-ci reçoit une copie de la référence à l’instance de classe. Les deux variables font référence au même objet. Le paramètre est une copie de la référence. La méthode appelée ne peut pas réaffecter l’instance dans la méthode appelante. Toutefois, la méthode appelée peut utiliser la copie de la référence pour accéder aux membres de l’instance. Si la méthode appelée modifie un membre d’instance, la méthode appelante voit également ces modifications, car elle fait référence à la même instance.
La sortie de l’exemple suivant illustre la différence. La méthode ClassTaker
change la valeur du champ willIChange
, car la méthode utilise l’adresse mentionnée dans le paramètre pour rechercher le champ spécifié de l’instance de classe. Le champ willIChange
du struct de la méthode d’appel ne change pas de l’appel à StructTaker
, car la valeur de l’argument est une copie du struct lui-même, et non pas une copie de son adresse. StructTaker
change la copie, et cette dernière est perdue quand l’appel à StructTaker
est terminé.
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
*/
Combinaisons de type de paramètre et de mode d’argument
Les modifications apportées à l’argument qui sont visibles à partir de l’appelant dépendent de la façon dont un argument est passé et de s’il s’agit d’un type référence ou d’un type valeur :
- Lorsque vous passez un type valeur par valeur :
- Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications ne sont pas visibles à partir de l’appelant.
- Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications ne sont pas visibles par l’appelant.
- Lorsque vous passez un type référence par valeur :
- Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications ne sont pas visibles à partir de l’appelant.
- Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications sont visibles à partir de l’appelant.
- Lorsque vous transmettez un type valeur par référence :
- Si la méthode affecte le paramètre pour faire référence à un autre objet à l’aide
ref =
de , ces modifications ne sont pas visibles à partir de l’appelant. - Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications sont visibles à partir de l’appelant.
- Si la méthode affecte le paramètre pour faire référence à un autre objet à l’aide
- Lorsque vous passez un type référence par référence :
- Si la méthode affecte le paramètre pour faire référence à un autre objet, ces modifications sont visibles à partir de l’appelant.
- Si la méthode modifie l’état de l’objet auquel le paramètre fait référence, ces modifications sont visibles à partir de l’appelant.
Le passage d’un type référence par référence permet à la méthode appelée de remplacer l’objet auquel fait référence le paramètre de référence dans l’appelant. L'emplacement de stockage de l'objet est passé à la méthode comme valeur du paramètre de référence. Si vous modifiez la valeur de l'emplacement de stockage du paramètre (pour pointer vers un nouvel objet), vous modifiez également l'emplacement de stockage auquel fait référence l'appelant. L'exemple suivant passe une instance d'un type référence en tant que paramètre 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
Contexte sécurisé des références et des valeurs
Les méthodes peuvent stocker les valeurs des paramètres dans des champs. Lorsque les paramètres sont passés par valeur, cela est généralement fait de façon sécurisée. Les valeurs sont copiées et les types de référence sont accessibles lorsqu’ils sont stockés dans un champ. La transmission de paramètres par référence de façon sûre nécessite que le compilateur définisse quand il est sûr d’affecter une référence à une nouvelle variable. Pour chaque expression, le compilateur définit un contexte sécurisé qui limite l’accès à une expression ou à une variable. Le compilateur utilise deux étendues : safe-context et ref-safe-context.
- L’étendue safe-context définit l’étendue dans laquelle n’importe quelle expression est accessible en toute sécurité.
- L’étendue ref-safe-context définit l’étendue dans laquelle une référence à n’importe quelle expression peut être accessible ou modifiée en toute sécurité.
De manière informelle, vous pouvez considérer ces étendues comme le mécanisme permettant de vous assurer que votre code n’accède jamais ou ne modifie jamais une référence qui n’est plus valide. Une référence est valide tant qu’elle fait référence à un objet ou à un struct valide. L’étendue safe-context définit quand une variable peut être affectée ou réaffectée. L’étendue ref-safe-context définit quand une variable peut être affectée par référence ou réaffectée par référence. L’affectation affecte une variable à une nouvelle valeur ; l’affectation par référence affecte la variable pour faire référence à un autre emplacement de stockage.
Paramètres de référence
Vous appliquez l’un des modificateurs suivants à une déclaration de paramètre pour passer des arguments par référence plutôt que par valeur :
ref
: l’argument doit être initialisé avant d’appeler la méthode. La méthode peut affecter une nouvelle valeur au paramètre, mais n’est pas obligée de le faire.out
: la méthode appelante n’est pas obligée d’initialiser l’argument avant d’appeler la méthode. La méthode doit affecter une valeur au paramètre.ref readonly
: l’argument doit être initialisé avant d’appeler la méthode. La méthode ne peut pas affecter de nouvelle valeur au paramètre.in
: l’argument doit être initialisé avant d’appeler la méthode. La méthode ne peut pas affecter de nouvelle valeur au paramètre. Le compilateur peut créer une variable temporaire pour contenir une copie de l’argument pour les paramètresin
.
Les membres d’une classe ne peuvent pas avoir de signatures qui diffèrent uniquement par ref
, ref readonly
, in
ou out
. Une erreur de compilation se produit si la seule différence entre deux membres d’un type est que l’un d’eux a un paramètre ref
et que l’autre un a un paramètre out
, ref readonly
ou in
. Toutefois, les méthodes peuvent être surchargées quand une méthode a un paramètre ref
, ref readonly
, in
, ou out
et que l’autre a un paramètre transmis par valeur, comme illustré dans l’exemple suivant. Dans d'autres situations nécessitant une correspondance de signature, comme le masquage ou la substitution, in
, ref
, ref readonly
et out
font partie de la signature et ne correspondent pas l’un à l’autre.
Lorsqu’un paramètre a l’un des modificateurs précédents, l’argument correspondant peut avoir un modificateur compatible :
- Un argument pour un paramètre
ref
doit inclure le modificateurref
. - Un argument pour un paramètre
out
doit inclure le modificateurout
. - Un argument pour un paramètre
in
peut éventuellement inclure le modificateurin
. Si le modificateurref
est utilisé sur l’argument à la place, le compilateur émet un avertissement. - Un argument pour un paramètre
ref readonly
doit inclure soit le modificateurin
, soit leref
, mais pas les deux. Si aucun modificateur n’est inclus, le compilateur émet un avertissement.
Lorsque vous utilisez ces modificateurs, ils décrivent comment l’argument est utilisé :
ref
signifie que la méthode peut lire ou écrire la valeur de l’argument.out
signifie que la méthode définit la valeur de l’argument.ref readonly
signifie que la méthode lit, mais ne peut pas écrire la valeur de l’argument. L’argument doit être passé par référence.in
signifie que la méthode lit, mais ne peut pas écrire la valeur de l’argument. L’argument est passé par référence ou par le biais d’une variable temporaire.
Vous ne pouvez pas utiliser les modificateurs de paramètres précédents dans les types de méthodes suivants :
- Méthodes async, que vous définissez à l’aide du modificateur async.
- Les méthodes Iterator, qui incluent une instruction yield return ou
yield break
.
Les méthodes d’extension ont également des restrictions sur l’utilisation de ces mots clés d’argument :
- Le
out
mot clé ne peut pas être utilisé sur le premier argument d’une méthode d’extension. - Le mot clé
ref
ne peut pas être utilisé sur le premier argument d’une méthode d’extension lorsque l’argument n’est pas unstruct
, ou qu’un type générique n’est pas contraint d’être un struct. - Les mots clés
ref readonly
etin
ne peuvent pas être utilisés, sauf si le premier argument est unstruct
. - Les mots clés
ref readonly
etin
ne peuvent pas être utilisés sur n’importe quel type générique, même s’ils sont contraints d’être un struct.
Les propriétés ne sont pas des variables. Ce sont des méthodes. Les propriétés ne peuvent pas être des arguments pour les paramètres ref
.
Modificateur de paramètre ref
Pour utiliser un paramètre ref
, la définition de la méthode et la méthode d'appel doivent utiliser explicitement le mot clé ref
, comme indiqué dans l'exemple suivant. (Sauf que la méthode appelante peut omettre ref
lors de l’appel COM.)
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Un argument passé à un paramètre ref
doit être initialisé avant d'être transmis.
Modificateur de paramètre out
Pour utiliser un paramètre out
, la définition de la méthode et la méthode d'appel doivent utiliser explicitement le mot clé out
. Par exemple :
int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44
void OutArgExample(out int number)
{
number = 44;
}
Les variables passées comme arguments out
n’ont pas à être initialisées avant d’être passées dans un appel de méthode. Toutefois, la méthode appelée doit assigner une valeur avant le retour de la méthode.
Les méthodes deconstruct déclarent leurs paramètres avec le modificateur out
pour retourner plusieurs valeurs. D’autres méthodes peuvent retourner des tuples de valeur pour plusieurs valeurs renvoyées.
Vous pouvez déclarer une variable dans une instruction distincte avant de la passer en tant qu’out
argument. Vous pouvez également déclarer la out
variable dans la liste d’arguments de l’appel de méthode au lieu de le faire dans une déclaration de variable distincte. Les déclarations de variable out
rendent le code plus simple et plus lisible, et vous empêchent également d’assigner par inadvertance une valeur à la variable avant l’appel de méthode. L’exemple suivant définit la variable number
dans l’appel de la méthode 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
Vous pouvez également déclarer une variable locale implicitement typée.
Modificateur ref readonly
Le modificateur ref readonly
doit être présent dans la déclaration de méthode. Un modificateur sur le site d’appel est facultatif. Vous pouvez utiliser le modificateur in
ou ref
. Le modificateur ref readonly
n’est pas valide sur le site d’appel. Le modificateur que vous utilisez sur le site d’appel peut vous aider à décrire les caractéristiques de l’argument. Vous ne pouvez utiliser ref
que si l’argument est une variable et est accessible en écriture. Vous ne pouvez utiliser in
que lorsque l’argument est une variable. Il peut être accessible en écriture ou en lecture seule. Vous ne pouvez pas ajouter ces modificateurs si l’argument n’est pas une variable, mais qu’il s’agit d’une expression. Les exemples suivants illustrent ces conditions. La méthode suivante utilise le modificateur ref readonly
pour indiquer qu’un struct volumineux doit être passé par référence pour des raisons de performances :
public static void ForceByRef(ref readonly OptionStruct thing)
{
// elided
}
Vous pouvez appeler la méthode à l’aide du modificateur ref
ou in
. Si vous omettez le modificateur, le compilateur émet un avertissement. Lorsque l’argument est une expression, et non une variable, vous ne pouvez pas ajouter les modificateurs in
ou ref
. Vous devez donc supprimer l’avertissement :
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
Si la variable est une variable readonly
, vous devez utiliser le modificateur in
. Le compilateur émet une erreur si vous utilisez le modificateur ref
à la place.
Le modificateur ref readonly
indique que la méthode s’attend à ce que l’argument soit une variable plutôt qu’une expression qui n’est pas une variable. Des constantes, des valeurs de retour de méthode et des propriétés sont des exemples d’expressions qui ne sont pas des variables. Si l’argument n’est pas une variable, le compilateur émet un avertissement.
Modificateur de paramètre in
Le modificateur in
est requis dans la déclaration de méthode, mais inutile sur le site d’appel.
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;
}
Le modificateur in
permet au compilateur de créer une variable temporaire pour l’argument et de passer une référence en lecture seule à cet argument. Le compilateur crée toujours une variable temporaire lorsque l’argument doit être converti, lorsqu’il existe une conversion implicite à partir du type d’argument ou lorsque l’argument est une valeur qui n’est pas une variable. Par exemple, lorsque l’argument est une valeur littérale ou la valeur retournée à partir d’un accesseur de propriété. Lorsque votre API nécessite que l’argument soit passé par référence, choisissez le modificateur ref readonly
et non le in
.
Les méthodes définies à l’aide des paramètres in
peuvent potentiellement optimiser les performances. Certains arguments de type struct
peuvent être volumineux et, quand les méthodes sont appelées dans des boucles serrées ou des chemins de code critiques, le coût de la copie de ces structures est important. Les méthodes déclarent des paramètres in
pour spécifier que des arguments peuvent être passés par référence en toute sécurité, car la méthode appelée ne modifie pas l’état de l’argument. Le passage de ces arguments par référence permet d’éviter une copie (potentiellement) coûteuse. Vous ajoutez explicitement le modificateur in
sur le site d’appel pour vérifier que l’argument est passé par référence, non par valeur. L’utilisation explicite de in
a les deux effets suivants :
- La spécification de
in
sur le site d’appel force le compilateur à sélectionner une méthode définie avec un paramètrein
correspondant. Sinon, quand deux méthodes diffèrent uniquement par la présence dein
, la surcharge par valeur convient mieux. - En spécifiant
in
, vous déclarez votre intention de passer un argument par référence. L’argument utilisé avecin
doit représenter un emplacement directement référençable. Les mêmes règles générales pour les argumentsout
etref
s’appliquent : vous ne pouvez pas utiliser de constantes, de propriétés ordinaires ou d’autres expressions qui produisent des valeurs. Sinon, l’omission dein
sur le site d’appel informe le compilateur qu’il est autorisé à créer une variable temporaire à passer par référence en lecture seule à la méthode. Le compilateur crée une variable temporaire pour surmonter plusieurs restrictions avec les argumentsin
:- Une variable temporaire autorise des constantes au moment de la compilation comme paramètres
in
. - Une variable temporaire autorise des propriétés ou d’autres expressions pour les paramètres
in
. - Une variable temporaire autorise des arguments pour lesquels il existe une conversion implicite de type d’argument en type de paramètre.
- Une variable temporaire autorise des constantes au moment de la compilation comme paramètres
Dans toutes les instances précédentes, le compilateur crée une variable temporaire qui stocke la valeur de la constante, la propriété ou une autre expression.
Le code suivant illustre ces règles :
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`
Supposons à présent qu’une autre méthode utilisant des arguments par valeur est disponible. Les résultats changent comme illustré dans le code suivant :
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`
Le seul appel de méthode dans lequel l’argument est passé par référence est le dernier.
Notes
Le code précédent utilise int
comme type d’argument par souci de simplicité. Comme int
n’est pas plus volumineux qu’une référence dans la plupart des ordinateurs modernes, il n’y aucun avantage à passer un seul int
comme référence en lecture seule.
Modificateur params
Dans une déclaration de méthode, vous ne pouvez pas spécifier d’autre paramètre après le mot clé params
et vous pouvez utiliser un seul mot clé params
.
Le type déclaré du paramètre params
doit être un type collection. Les types collection reconnus sont :
- Un type tableau
T[]
unidimensionnel, auquel cas le type élément estT
. - Un type étendue :
System.Span<T>
System.ReadOnlySpan<T>
Ici, le type élément estT
.
- Un type avec une méthode create accessible avec un type élément correspondant. La méthode create est identifiée en utilisant le même attribut que celui utilisé pour les expressions de collection.
- Un struct ou un type classe qui implémente System.Collections.Generic.IEnumerable<T>, où :
- Le type a un constructeur qui peut être appelé sans arguments, et le constructeur est au moins aussi accessible que le membre déclarant.
- Le type a une méthode (pas une extension) d’instance
Add
, où :- La méthode peut être appelée avec un seul argument de valeur.
- Si la méthode est générique, les arguments de type peuvent être inférés de l’argument.
- La méthode est au moins aussi accessible que le membre déclarant. Ici, le type élément est le type itération du type.
- Un type interface :
Avant C# 13, le paramètre doit être un tableau unidimensionnel.
Lorsque vous appelez une méthode avec un paramètre params
, vous pouvez passer :
- Une liste séparée par des virgules d’arguments du type des éléments de tableau.
- Une collection d’arguments du type spécifié.
- Aucun argument. Si vous n’envoyez aucun argument, la longueur de la liste
params
est égale à zéro.
L’exemple suivant montre différentes façons d’envoyer des arguments à un paramètre 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 résolution de surcharge peut entraîner une ambiguïté lorsque l’argument d’un params
paramètre est un type de collection. Le type de collection de l’argument doit être convertible en type de collection du paramètre. Lorsque différentes surcharges fournissent de meilleures conversions pour ce paramètre, cette méthode peut être meilleure. Toutefois, si l’argument du params
paramètre est des éléments discrets ou manquants, toutes les surcharges avec différents params
types de paramètres sont égales à ce paramètre.
Pour plus d’informations, consultez la section sur les listes d’arguments dans la spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.