148 votes

Comment les chaînes de caractères sont-elles transmises en .NET ?

Lorsque je passe un string à une fonction, est-ce qu'un pointeur vers le contenu de la chaîne est transmis, ou est-ce que la chaîne entière est transmise à la fonction sur la pile comme un fichier struct serait ?

338voto

Justin Morgan Points 12853

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 :

  1. Les chaînes de caractères sont des types de référence en C#.
  2. 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.
  3. 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 :

3 votes

@TheLight - Désolé, mais vous êtes incorrect ici quand vous dites : "Un type de référence est transmis par référence par défaut." Par défaut, tous les paramètres sont passés par valeur, mais avec les types de référence, cela signifie que . la référence est transmise par valeur. Vous confondez les types de référence avec les paramètres de référence, ce qui est compréhensible car la distinction est très confuse. Voir le Section Passing Reference Types by Value ici. L'article que vous avez cité est tout à fait correct, mais il appuie en fait mon point de vue.

1 votes

@JustinMorgan Je ne veux pas faire remonter un fil de commentaires mort, mais je pense que le commentaire de TheLight a du sens si vous pensez en C. En C, les données sont juste un bloc de mémoire. Une référence est un pointeur vers ce bloc de mémoire. Si vous passez le bloc de mémoire entier à une fonction, cela s'appelle "passer par valeur". Si vous passez le pointeur, cela s'appelle "passer par référence". En C#, il n'y a pas de notion de passage de l'ensemble du bloc de mémoire, donc ils ont redéfini "passage par valeur" pour signifier le passage du pointeur. Cela ne semble pas correct, mais un pointeur est aussi un bloc de mémoire ! Pour moi, la terminologie est assez arbitraire.

0 votes

@roliu - Le problème est que nous ne travaillons pas en C, et que C# est extrêmement différent malgré son nom et sa syntaxe similaires. Pour une chose, les références ne sont pas les mêmes que les pointeurs et les considérer de cette manière peut conduire à des pièges. Le plus gros problème, cependant, c'est que le "passage par référence" a un impact sur l'environnement. très spécifique en C#, ce qui nécessite l'utilisation de l'option ref mot-clé. Pour prouver que le passage par référence fait une différence, voyez cette démo : rextester.com/WKBG5978

28voto

dasblinkenlight Points 264350

Les chaînes de caractères en C# sont des objets de référence immuables. Cela signifie que leurs références sont transmises (par valeur) et qu'une fois qu'une chaîne est créée, vous ne pouvez pas la modifier. Les méthodes qui produisent des versions modifiées de la chaîne (sous-chaînes, versions coupées, etc.) créent des objets de référence modifiés. copies de la chaîne originale.

14voto

Enigmativity Points 26345

Les chaînes de caractères sont des cas particuliers. Chaque instance est immuable. Lorsque vous modifiez la valeur d'une chaîne, vous allouez une nouvelle chaîne en mémoire.

Ainsi, seule la référence est transmise à votre fonction, mais lorsque la chaîne est éditée, elle devient une nouvelle instance et ne modifie pas l'ancienne.

4 votes

Les cordes sont no un cas particulier à cet égard. Il est très facile de créer des objets immuables qui pourraient avoir la même sémantique. (C'est-à-dire, une instance d'un type qui n'expose pas une méthode pour le muter...)

0 votes

Les chaînes de caractères sont des cas particuliers - elles sont effectivement des types de référence immuables qui semblent être mutables car elles se comportent comme des types de valeur.

0 votes

StringBuilder est une classe de chaîne mutable qui permet de modifier rapidement la chaîne en cours de construction sans allouer de nouvelles chaînes en mémoire pour chaque modification.

-3voto

Scott Mitchell Points 9

En C#, tous les types de données simples sont transmis par valeur, sauf si vous utilisez le mot clé "ref" pour transmettre par référence. Les instances des classes System et Custom sont généralement transmises par référence.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X