Menu

Paramètres ref, out et in en C# : le passage par référence expliqué

Comment fonctionnent les paramètres ref, out et in en C# : passer une variable plutôt qu'une copie, out pour des résultats supplémentaires et le schéma TryParse, out var, les paramètres in en lecture seule, les règles d'affectation définie, et ce qui change quand l'argument est un type référence.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Par défaut, C# passe chaque argument par valeur : la méthode reçoit une copie, et affecter le paramètre n'affecte pas l'appelant. Les modificateurs ref, out et in passent à la place la variable de l'appelant elle-même.

Sortie :

by value: 50
by ref:   60

Le mot-clé apparaît deux fois : dans la liste des paramètres (ref int score) et à l'endroit de l'appel (ref score). L'écrire à l'appel est obligatoire, et c'est voulu : quiconque lit AddPoints(ref score) sait que score peut changer. Oubliez-le et le compilateur signale CS1620, Argument 1 must be passed with the 'ref' keyword.

ref : lire et écrire la variable de l'appelant

Un paramètre ref est un alias de la variable de l'appelant. La méthode peut lire la valeur actuelle et la remplacer. L'exemple classique est l'échange, impossible avec de simples paramètres :

Sortie :

Linus Ada
volume 100

Règles pour ref :

  • L'argument doit être une variable (une locale, un paramètre, un champ ou un élément de tableau), pas un littéral ni une expression. Une propriété ne convient pas non plus : Clamp(ref player.Volume, 0, 100) donne l'erreur CS0206, A non ref-returning property or indexer may not be used as an out or ref value. Copiez la propriété dans une variable locale, passez celle-ci, puis réaffectez-la.
  • La variable doit être définitivement affectée avant l'appel. int n; Clamp(ref n, 0, 10); donne CS0165, Use of unassigned local variable 'n'.

out : des résultats supplémentaires

out sert aux valeurs que la méthode produit. L'appelant n'a pas besoin d'initialiser la variable, et la méthode doit l'affecter sur chaque chemin avant de revenir :

static void Split(string fullName, out string first, out string last)
{
    // error CS0177: The out parameter 'last' must be assigned to before control leaves the current method
    first = fullName.Split(' ')[0];
}

Dans la méthode, un paramètre out compte comme non affecté tant que vous n'y avez pas écrit, donc vous ne pouvez pas lire l'ancienne valeur de l'appelant. C'est la différence avec ref : ref fonctionne dans les deux sens, out dans un seul, de la méthode vers l'appelant.

Le schéma TryParse

L'usage le plus connu de out est le schéma Try de la bibliothèque de base : renvoyer un bool pour indiquer le succès et placer le résultat dans un paramètre out, pour qu'une entrée invalide ne lève jamais d'exception :

Sortie :

"vip25" is a coupon: 25% off
"42" is a quantity: 42
"BOGUS" is not recognized
"" is not recognized

out var quantity (C# 7.0) déclare la variable directement dans l'appel ; son type vient du paramètre. En cas d'échec, TryParse la met à 0 et renvoie false. Quand seul le succès de l'analyse vous intéresse, ignorez la valeur avec out _ : bool isNumber = int.TryParse(input, out _);.

Remarquez que TryGetDiscount affecte rate sur les deux chemins : une fois explicitement, et une fois en le transmettant à Dictionary.TryGetValue, qui est lui-même un appel out.

out ou renvoyer un tuple

Avant C# 7, out était le principal moyen de renvoyer plusieurs valeurs. Les tuples le font maintenant plus directement :

// with out parameters
static void MinMax(int[] values, out int min, out int max) { ... }
MinMax(temps, out int lo, out int hi);

// with a tuple
static (int Min, int Max) MinMax(int[] values) { ... }
var (lo, hi) = MinMax(temps);

Utilisez un tuple (ou une petite classe) pour de simples résultats multiples. Gardez out pour le schéma Try, où le retour bool permet de placer l'appel dans une condition if.

Types référence : modifier l'objet ou le remplacer

Passer une instance de classe par valeur permet déjà à la méthode de modifier l'objet, car la valeur copiée est une référence vers le même objet. ref ajoute une capacité de plus : remplacer l'objet vers lequel pointe la variable de l'appelant.

Sortie :

after AddItem:      2 items
after ResetByValue: 2 items
after ResetByRef:   0 items

ResetByValue a fait pointer sa propre copie de la référence vers une nouvelle liste ; l'appelant ne l'a jamais vu. ResetByRef a modifié la variable de l'appelant elle-même. Dans le code courant, vous avez rarement besoin de ref sur un paramètre de classe : renvoyez plutôt le nouvel objet.

Pour les structs, la différence est plus grande, puisqu'un argument struct est copié champ par champ. Une méthode qui doit modifier la struct d'un appelant (un Point, une grande struct de paramètres) a besoin de ref.

in : par référence en lecture seule

C# 7.2 a ajouté in, qui passe par référence mais interdit l'affectation. Son but est la performance : une grande struct n'est pas copiée à chaque appel, et l'appelant a la garantie qu'elle ne changera pas.

// C# 7.2 and later
struct Matrix4
{
    public double M11, M12, M13, M14, M21, M22, M23, M24,
                  M31, M32, M33, M34, M41, M42, M43, M44;
}

static double Trace(in Matrix4 m) => m.M11 + m.M22 + m.M33 + m.M44;

static void Reset(in Matrix4 m)
{
    m.M11 = 0;
    // error CS8332: Cannot assign to a member of variable 'm' or use it as the right hand side of a ref assignment because it is a readonly variable
}

À l'endroit de l'appel, in est facultatif : Trace(matrix) et Trace(in matrix) fonctionnent tous deux. Pour int, double, DateTime et les autres petits types, in n'apporte rien et peut être légèrement plus lent ; utilisez-le pour des structs de plusieurs champs que vous passez souvent.

Restrictions

  • Les paramètres ref et out ne peuvent pas être optionnels (CS1741). Un paramètre in le peut : static void Connect(in int retries = 3) est légal.
  • Des surcharges ne peuvent pas différer uniquement par ref et out (ou in), car ils sont identiques au niveau du runtime. F(int x) et F(ref int x) peuvent coexister.
  • Les méthodes async et les itérateurs (méthodes qui utilisent yield return) ne peuvent pas avoir de paramètres ref, out ou in. Renvoyez plutôt un tuple ou un objet résultat depuis ces méthodes.
  • Une lambda ou une fonction locale ne peut pas capturer un paramètre ref, out ou in de la méthode englobante ; copiez-le d'abord dans une variable locale.

ref, out et in comparés

L'appelant doit affecter avantLa méthode doit affecterLa méthode peut lireMot-clé à l'appel
(aucun)ouinonoui (une copie)aucun
refouinonouiobligatoire
outnonoui, sur chaque cheminseulement après affectationobligatoire
inouiinterditouifacultatif

Questions fréquentes

Quelle est la différence entre ref et out en C# ?

Les deux passent la variable de l'appelant elle-même plutôt qu'une copie. Avec ref, la variable doit être affectée avant l'appel, et la méthode peut la lire et la modifier. Avec out, la variable n'a pas besoin d'être affectée au préalable, la méthode ne peut pas la lire avant d'y écrire, et elle doit l'affecter avant de revenir (sinon CS0177). out signifie « ceci est un résultat supplémentaire ».

Que signifie out var en C# ?

Depuis C# 7.0, vous pouvez déclarer la variable out dans l'appel : if (int.TryParse(text, out var number)). Le type de la variable est déduit du paramètre, et elle reste visible après l'instruction. out _ ignore un résultat dont vous n'avez pas besoin.

Les objets sont-ils passés par référence en C# ?

Non, chaque argument est passé par valeur sauf si vous écrivez ref, out ou in. Pour une classe, la valeur copiée est une référence, donc la méthode peut modifier les champs de l'objet et l'appelant le voit. Ce qu'elle ne peut pas faire sans ref, c'est faire pointer la variable de l'appelant vers un autre objet.

Qu'est-ce que le modificateur de paramètre in en C# ?

in (C# 7.2 et plus) passe un argument par référence mais en lecture seule : la méthode ne peut pas l'affecter. Il sert à éviter de copier de grandes structs à chaque appel. Pour des petits types comme int ou DateTime, il n'apporte rien.

Pourquoi obtient-on « Argument 1 must be passed with the 'ref' keyword » ?

La méthode déclare un paramètre ref et l'appel a omis le mot-clé. C# exige ref (et out) aussi à l'endroit de l'appel, pour que le lecteur voie que la variable peut changer : Increment(ref count);, et non Increment(count);.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER