Une référence est transmise ; cependant, ce n'est pas techniquement transmis par référence. Il s'agit d'une distinction subtile, mais très importante. Considérez le code suivant :
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
Il y a trois choses que vous devez savoir pour comprendre ce qui se passe ici :
- Les chaînes de caractères sont des types de référence en C#.
- Elles sont également immuables, de sorte qu'à chaque fois que vous faites quelque chose qui donne l'impression que vous modifiez la chaîne, ce n'est pas le cas. Une toute nouvelle chaîne est créée, la référence est pointée sur elle, et l'ancienne est jetée.
- Même si les chaînes de caractères sont des types de référence,
strMain
n'est pas passé par référence. Il s'agit d'un type de référence, mais la référence elle-même est passé par valeur . Chaque fois que vous passez un paramètre sans le ref
mot-clé (sans compter out
paramètres), vous avez transmis quelque chose par valeur.
Cela doit donc signifier que vous... passez une référence par valeur. Puisque c'est un type de référence, seule la référence a été copiée sur la pile. Mais qu'est-ce que ça veut dire ?
Passage de types de référence par valeur : Vous le faites déjà
Les variables C# sont soit types de référence o types de valeurs . C#, les paramètres sont soit passé par référence o passé par valeur . La terminologie est un problème ici ; ces termes semblent être la même chose, mais ce n'est pas le cas.
Si vous passez un paramètre de TOUT type, et que vous n'utilisez pas la fonction ref
alors vous l'avez transmis par valeur. Si vous l'avez transmis par valeur, ce que vous avez réellement transmis est une copie. Mais si le paramètre était un type de référence, alors la chose que vous avez copiée était le fichier référence, pas ce qu'il pointait.
Voici la première ligne de la Main
méthode :
string strMain = "main";
Nous avons créé deux choses sur cette ligne : une chaîne de caractères avec la valeur main
stockée quelque part en mémoire, et une variable de référence appelée strMain
en la pointant du doigt.
DoSomething(strMain);
Maintenant, nous passons cette référence à DoSomething
. Nous l'avons passé par valeur, ce qui signifie que nous avons fait une copie. Il s'agit d'un type de référence, ce qui signifie que nous avons copié la référence, et non la chaîne elle-même. Nous avons maintenant deux références qui pointent chacune vers la même valeur en mémoire.
A l'intérieur de l'appelant
Voici le haut de la DoSomething
méthode :
void DoSomething(string strLocal)
Non ref
mot-clé, donc strLocal
y strMain
sont deux références différentes pointant sur la même valeur. Si nous réassignons strLocal
...
strLocal = "local";
...nous n'avons pas changé la valeur stockée ; nous avons pris la référence appelée strLocal
et a visé une toute nouvelle corde. Qu'arrive-t-il à strMain
quand on fait ça ? Rien. Il pointe toujours vers l'ancienne chaîne.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Immutabilité
Changeons de scénario pour une seconde. Imaginez que nous ne travaillons pas avec des chaînes de caractères, mais avec un type de référence mutable, comme une classe que vous avez créée.
class MutableThing
{
public int ChangeMe { get; set; }
}
Si vous suivez la référence objLocal
à l'objet vers lequel il pointe, vous pouvez modifier ses propriétés :
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
Il n'y a toujours qu'un seul MutableThing
dans la mémoire, et la référence copiée et la référence originale pointent toujours vers elle. Les propriétés de la MutableThing
eux-mêmes ont changé :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
Ah, mais les chaînes de caractères sont immuables ! Il n'y a pas de ChangeMe
à définir. Vous ne pouvez pas faire strLocal[3] = 'H'
en C# comme vous le feriez avec un langage C. char
vous devez construire une toute nouvelle chaîne de caractères à la place. La seule façon de modifier strLocal
est de pointer la référence vers une autre chaîne, et cela signifie que rien de ce que vous faites pour strLocal
peut affecter strMain
. La valeur est immuable, et la référence est une copie.
Passage d'une référence par référence
Pour prouver qu'il y a une différence, voici ce qui se passe lorsque vous passez une référence par référence :
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
Cette fois, la chaîne de caractères dans Main
est réellement modifié parce que vous avez transmis la référence sans la copier sur la pile.
Ainsi, même si les chaînes sont des types de référence, le fait de les transmettre par valeur signifie que tout ce qui se passe dans le destinataire n'affectera pas la chaîne dans l'appelant. Mais comme elles son les types de référence, vous n'avez pas besoin de copier la chaîne entière en mémoire lorsque vous voulez la faire circuler.
Autres ressources :