Skip to content
22 juin / Anna YAFI

Passage en paramètre en C# : par valeur ou par référence ?

On entend souvent parler du passage en paramètre par valeur et par référence (pass-by-value/pass-by-reference), il est important de savoir de quoi il s’agit pour développer sans erreur. Et pourtant, nos idées sont-elles toujours bien au clair sur ce sujet ? S’il arrive que l’on ne fasse pas d’erreur à ce sujet en développant, c’est bien souvent grâce à l’intuition formée par l’expérience. Mais il est toujours bon d’asseoir rationnellement ses bases, afin d’ôter tout semblant de doute…

Les types valeurs en paramètre

Lors du passage en paramètre d’une variable de type valeur, une copie de la variable est alors créée en mémoire. Et c’est sur cette copie que va travailler la fonction appelée. Notre fonction traite alors une copie de la variable et non la variable elle-même.

Voici pour nos futurs exemples notre classe de fonctions :

public class Modificateur

{

public static void Addition(int i)

{

i += 2;

}

public static void Addition(ref int i)

{

i += 2;

}
<!--more-->
public static void ChangerNom(Personne p)

{

p.Nom = "Toto2";

}

public static void ChangerPersonne(Personne p, Personne p2)

{

p = p2;

}

public static void ChangerPhrase(string phrase)

{

phrase = "Mais je préfère comprendre.";

}

internal static void ChangerPhrase(ref string phrase)

{

phrase = "Mais je préfère comprendre.";

}

}

Et notre classe personne :

public class Personne

{

public Personne(string nom)

{

this.Nom = nom;

}

public string Nom { get; set; }

}

Dans notre cas ci-dessous, i est toujours égal à 2 car Addition(i) travaille sur une copie du type valeur qu’est i :

static void Main(string[] args)

{

int i = 2;

Modificateur.Addition(i);

Console.WriteLine(i); //i=2;

Pour information, il n’y a pas de différence avec les types prédéfinis de l’espace de noms System qui sont des struct et donc des types valeur :

Int32 iStruct = 2;

Modificateur.Addition(iStruct);

Console.WriteLine(iStruct);//i=2;

Maintenant que se passe-t-il s’il s’agit d’un objet personne qu’on modifie ?

Les types références en paramètre

Personne p = new Personne("Toto");

Modificateur.ChangerNom(p);

Console.WriteLine(p.Nom); //p.Nom="Toto2"

Ici p.Nom a changé, ce n’est plus « Toto », mais « Toto2 », l’objet passé en paramètre a bien été modifié, contrairement à int i qui précédemment, n’était pas modifié. Cela signifie-t-il que contrairement aux types valeurs, les types références sont eux-mêmes passés en paramètre ? Testons-le par ce cas :

Personne personne  = new Personne("Toto");

Personne personne2 = new Personne("Toto2");

Modificateur.ChangerPersonne(personne,personne2);

Console.WriteLine(personne.Nom); //personne.Nom="Toto";

Dans notre test ci-dessus, personne.Nom reste bien « Toto ». L’objet n’est donc pas passé lui-même en paramètre. Mais il faut savoir que ce n’est pas non plus une copie de l’objet qui est passée. C’est une copie de la référence qui est passée en paramètre ! La fonction appelée travaillera donc sur des copies de vos références. C’est pourquoi la fonction ci-dessus ne modifie pas notre objet personne en personne2. Car ChangerPersonne(personne, personne2) ne fera qu’intervertir des copies de références et non vos références elles-mêmes. Par contre, dans l’avant-dernière fonction exposée, une copie de la référence à l’objet p est certes passée en paramètre, mais il n’en reste pas moins que toutes les références, copies ou non, pointent vers le même objet et modifient le même objet.

On le voit bien dans le schéma ci-dessous, n’importe laquelle des quatre références pourraient modifier l’objet p, celui-ci changera. Si référence 1 modifie p, au retour dans le main, référence, référence 2 ou référence 3 nous rendraient un p modifié.

Reference vers objet

Et les string ? traitement particulier ?

Il faut cependant consacrer un petit paragraphe aux chaînes de caractères, un petit peu particulières. Ce sont des types référence. Ainsi, dans le cas ci-dessous, la variable phrase devrait être modifiée :

string phraseDuMain = "J'aime apprendre par coeur.";

Modificateur.ChangerPhrase(phraseDuMain);

Console.WriteLine(phraseDuMain);//phraseDuMain = "J'aime apprendre par coeur."

Et pourtant non ! La phrase reste égale à « J’aime apprendre par cœur ». Les string sont pourtant des types références, et selon ce qui a été expliqué plus haut : une copie de la référence à la valeur est passée en paramètre mais cela n’empêche pas de modifier l’objet lui-même. Mais rappelons-nous, les string sont des types références bien particuliers : ils sont immutables. C’est-à-dire qu’on ne peut les modifier directement, et lorsque l’on modifie un string dans un programme, c’est un autre string qui est crée en mémoire, comme l’explique Microsoft (http://msdn.microsoft.com/en-us/library/362314fe.aspx) :

Strings are immutable–the contents of a string object cannot be changed after the object is created, although the syntax makes it appear as if you can do this. For example, when you write this code, the compiler actually creates a new string object to hold the new sequence of characters, and that new object is assigned to b. The string “h” is then eligible for garbage collection.

string b = "h";
b += "ello";

Ainsi, lorsque je passe phrase en paramètre, une copie de la référence à la valeur est passée. Si je modifie cette valeur, c’est un autre string qui est créé et qui va être référencé par ma copie de référence et non par la référence originale dans le Main().

string phraseDuMain = "J'aime apprendre par coeur.";

Modificateur.ChangerPhrase(phraseDuMain);

Console.WriteLine(phraseDuMain);//phraseDuMain = "J'aime apprendre par coeur."

Ainsi, ci-dessus, ma référence originale dans le Main() (référence1), phraseDuMain, référencie un string en mémoire « J’aime apprendre par cœur. Lorsque phraseDuMain est passé en paramètre, c’est une copie de la référence qui est passée, tout comme les autres types références classiques. Référence2 référencie donc le même string. Mais lorsque Modificateur.ChangerPhrase(string phrase) modifie phrase, à la différence d’un objet classique, un autre string est créé en mémoire, le premier est déréférencé par référence 2 qui se met à référencer le deuxième « Mais je préfère apprendre » (changements locaux en vert). Référence 2 référencie donc bien le nouveau string « Mais je préfère comprendre » mais pas référence 1 qui continue de référencer le premier string. Il est donc normal que lorsqu’on revient dans le Main(), phraseDuMain est toujours égal à « J’aime apprendre par cœur. » Les strings n’ont donc pas de traitement de faveur, ils sont aussi passés par copie de référence tout comme les autres types références. Seulement, dû à leur nature d’immutable, les changements ne peuvent se répercuter à la fonction appelante car un autre objet a été créé, assigné à la copie de la référence et non à la référence originale.

Si vous voulez absolument travailler avec les références originales, dans le cas de types valeurs comme dans le cas de types références, vous pouvez précéder vos paramètres du mot clé ref. :

int j = 2;

Modificateur.Addition(ref j);

Console.WriteLine(j); //j = 4;

Le mot clé ref passe donc à la fonction la référence originale. Dans le cas de type valeur comme ci-dessus, la référence à la valeur j est passée, ce qui permet de modifier j lui-même. Vous pouvez également tester l’exemple des string précédents :

string phraseDuMain2 = "J'aime apprendre par coeur.";

Modificateur.ChangerPhrase(ref phraseDuMain2);

Console.WriteLine(phraseDuMain2);//phraseDuMain2="Mais je préfère comprendre."

La référence originale phraseDuMain2 est passée à la fonction ChangerPrhase(ref string phrase). C’est donc la référence originale qui se met à référencer le nouveau string.

Il y a également le mot clé out, très semblable au mot clé ref, à l’exception que ref exige que la variable soit initialisée avant son passage en paramètre. Cela dit ref et out ne sont pas traités de la même manière au runtime comme le précise Microsoft.

Vous pourrez vous demander pourquoi Microsoft n’a pas choisi de construire un langage où le passage en paramètre se ferait par défaut avec les références originales (en anglais, call-by reference language / call-by value language). Dans Application development using C# and .NET de Michael Stiefel, Robert J. Oberg, on peut lire :

Call-by-value is “safe,” because the method never directly accesses the actual parameters, only its own local copies. But there are drawbacks to call-by-value:
There is no direct way to modify the value of an argument. You may use the return type of the method, but that only allows you to pass one value back to the calling program.
There is overhead in copying a large object.
The overhead in copying a large object is borne when you pass a struct instance. If you pass a class instance, or an instance of any other reference type, you are passing only a reference and not the actual data itself. This may sound like “call-by-reference,” but what you are actually doing is passing a reference by value.

.

Vous pourrez lire de nombreux articles sur le passage par valeur ou par référence, faites attention aux mots employés, les termes varient d’un article à l’autre et chacun les emploie avec un sens différent. Cela dit, si vous avez compris le fond du mécanisme, vous n’avez plus peur des mots !

No TweetBacks yet. (Be the first to Tweet this post)

Autres articles:
  1. SharePoint : développement et performances Nous allons voir comment l’on peut préserver voire améliorer les performances de SharePoint grâce à quelques bonnes pratiques de code....
  2. ORM .Net : Spot sur Nhibernate Dans la perspective de performance, de facilité et de gain de temps de développement des applications, les ORM font de...
  3. MVVM : Vers des applications plus performantes et plus maintenables MVVM (Model-View-ViewModel) est l’un des nouveaux design patterns orientés plateformes .Net de développement modernes (WPF et Silverlight). Dans cet article,...
Laisser un commentaire

Notifiez-moi les commentaires à venir via email. Vous pouvez aussi vous abonner sans commenter.