Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesIntermédiaireSérie : C# / .NET

C# : Passage de paramètres

Passage par valeur, par référence (ref), out, in - comment C# transmet les données aux méthodes et ce que ça implique en mémoire.

4 min de lecture
Mis à jour il y a 4 mois
c#csharprefoutinparamètresmémoirevalue typereference type

C# : Passage de paramètres

Quand tu appelles une méthode avec des arguments, que se passe-t-il exactement ? Est-ce que la méthode travaille sur les données originales ou sur une copie ? La réponse dépend du type et du mot-clé utilisé.

Si tu n'as pas encore lu l'article sur la mémoire, commence par là - la distinction entre stack et heap est essentielle pour comprendre ce qui suit.


# Passage par défaut (par valeur)

Par défaut, C# passe une copie de la variable à la méthode. Mais ce qui est copié dépend du type :

Avec un value type

La valeur elle-même est copiée. La méthode travaille sur sa propre copie - l'original ne bouge pas.

void Doubler(int nombre)
{
    nombre *= 2;  // modifie la copie locale
}
 
int x = 10;
Doubler(x);
Console.WriteLine(x);  // 10  -  x n'a pas changé

nombre dans Doubler est une copie indépendante de x. Modifier nombre ne touche pas x.

Passage par valeur  -  la méthode reçoit une copie, l'original reste inchangé
Passage par valeur - la méthode reçoit une copie, l'original reste inchangé

Avec un reference type

La référence (l'adresse mémoire) est copiée, pas l'objet. Les deux variables pointent donc vers le même objet sur le heap.

void AjouterElement(List<int> liste)
{
    liste.Add(99);  // modifie l'objet original !
}
 
List<int> nombres = new List<int> { 1, 2, 3 };
AjouterElement(nombres);
Console.WriteLine(nombres.Count);  // 4  -  l'objet a été modifié

La méthode reçoit une copie de la référence, mais cette copie pointe vers le même objet. Modifier l'objet via la copie modifie l'original.

Passage d'un reference type  -  les deux références pointent vers le même objet
Passage d'un reference type - les deux références pointent vers le même objet

Attention au piège

Copier la référence et modifier l'objet, c'est différent de réassigner la référence :

void Remplacer(List<int> liste)
{
    liste = new List<int> { 99, 100 };  // crée un NOUVEL objet
    // la variable locale 'liste' pointe maintenant vers le nouveau
    // mais l'appelant pointe toujours vers l'ancien
}
 
List<int> nombres = new List<int> { 1, 2, 3 };
Remplacer(nombres);
Console.WriteLine(nombres.Count);  // 3  -  inchangé !

liste = new List<>() ne modifie que la copie locale de la référence. L'appelant garde son objet original.


# Le mot-clé ref

ref passe la variable elle-même, pas une copie. La méthode a un accès direct à la variable de l'appelant.

Avec un value type

void Doubler(ref int nombre)
{
    nombre *= 2;
}
 
int x = 10;
Doubler(ref x);
Console.WriteLine(x);  // 20  -  x a été modifié directement

Sans ref, x reste à 10. Avec ref, la méthode modifie x sur la stack de l'appelant.

ref sur value type  -  accès direct à la variable de l'appelant
ref sur value type - accès direct à la variable de l'appelant

Avec un reference type

ref sur un reference type permet de réassigner la variable de l'appelant - ce qui est impossible par défaut :

void Remplacer(ref List<int> liste)
{
    liste = new List<int> { 99, 100 };
}
 
List<int> nombres = new List<int> { 1, 2, 3 };
Remplacer(ref nombres);
Console.WriteLine(nombres.Count);  // 2  -  nombres pointe maintenant vers la nouvelle liste !

C'est la différence clé : sans ref, réassigner dans la méthode ne change rien pour l'appelant. Avec ref, ça le change.

ref sur reference type  -  la méthode réassigne la variable de l'appelant
ref sur reference type - la méthode réassigne la variable de l'appelant

Contrainte

La variable doit être initialisée avant d'être passée en ref. Le compilateur refuse sinon.

int x;            // pas initialisé
Doubler(ref x);   // Erreur de compilation !

# Le mot-clé out

out est le cousin de ref. La différence : la méthode doit assigner une valeur au paramètre, et la variable n'a pas besoin d'être initialisée avant l'appel.

bool TryDivide(int a, int b, out int resultat)
{
    if (b == 0)
    {
        resultat = 0;  // obligatoire, même en cas d'échec
        return false;
    }
    resultat = a / b;
    return true;
}
 
if (TryDivide(10, 3, out int res))
{
    Console.WriteLine(res);  // 3
}

Le pattern le plus courant : les méthodes TryXxx qui retournent un bool (succès/échec) et remplissent le résultat via out.

ref = "je lis et je modifie ta variable". out = "je t'écris une valeur dedans, peu importe ce qu'il y avait avant".


# Le mot-clé in (C# 7.2+)

in est l'inverse de ref : la variable est passée par référence mais la méthode ne peut pas la modifier. C'est une référence en lecture seule.

void Afficher(in int valeur)
{
    Console.WriteLine(valeur);
    // valeur = 42;  // Erreur de compilation ! in = lecture seule
}
 
int x = 10;
Afficher(in x);  // le 'in' côté appelant est optionnel
Afficher(x);     // fonctionne aussi

Quand l'utiliser ?

in est utile pour les gros structs. Passer un struct par valeur copie toutes ses données. Avec in, on passe une référence (pas de copie) tout en garantissant que la méthode ne modifie rien.

// Sans in : le struct est copié entièrement à chaque appel
void Traiter(GrosStruct data) { }
 
// Avec in : seule une référence est passée, pas de copie
void Traiter(in GrosStruct data) { }

Pour les types simples (int, bool...), in n'apporte rien - la copie est déjà aussi rapide qu'un passage par référence.


# Récapitulatif

Mot-cléCopie ?LectureÉcritureInitialisation requise
(aucun)OuiOuiLocale seulementOui
refNonOuiOui (modifie l'original)Oui
outNonNon (avant assignation)ObligatoireNon
inNonOuiNon (lecture seule)Oui

# La suite

Tu maîtrises maintenant tous les modes de passage de paramètres en C#. Les prochains articles de la série :