Lorsque je passe un string
à une fonction, un pointeur vers le contenu de la chaîne est-il passé ou la chaîne entière est-elle transmise à la fonction sur la pile comme le struct
serait un?
Lorsque je passe un string
à une fonction, un pointeur vers le contenu de la chaîne est-il passé ou la chaîne entière est-elle transmise à la fonction sur la pile comme le struct
serait un?
Réponses:
Une référence est passée; cependant, il n'est pas techniquement passé 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:
strMain
ne sont pas passées par référence. C'est un type de référence, mais la référence elle-même est passée par valeur . Chaque fois que vous passez un paramètre sans le ref
mot - clé (sans compter les out
paramètres), vous avez transmis quelque chose par valeur.Cela doit donc signifier que vous ... passez une référence par valeur. Puisqu'il s'agit d'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?
Les variables C # sont des types référence ou des types valeur . Les paramètres C # sont passés par référence ou par valeur . La terminologie est un problème ici; cela ressemble à la même chose, mais ce n'est pas le cas.
Si vous passez un paramètre de type ANY et que vous n'utilisez pas le ref
mot - clé, vous l'avez passé par valeur. Si vous l'avez passé par valeur, ce que vous avez vraiment transmis était une copie. Mais si le paramètre était un type de référence, alors la chose que vous avez copiée était la référence, pas celle vers laquelle elle 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 avec la valeur main
stockée quelque part en mémoire, et une variable de référence appelée strMain
pointant vers elle.
DoSomething(strMain);
Maintenant, nous passons cette référence à DoSomething
. Nous l'avons passé par valeur, ce qui signifie que nous en avons fait une copie. C'est un type de référence, ce qui signifie que nous avons copié la référence, pas la chaîne elle-même. Nous avons maintenant deux références qui pointent chacune vers la même valeur en mémoire.
Voici le top de la DoSomething
méthode:
void DoSomething(string strLocal)
Pas de ref
mot-clé, donc strLocal
et strMain
sont deux références différentes pointant sur la même valeur. Si nous réaffectons strLocal
...
strLocal = "local";
... nous n'avons pas changé la valeur stockée; nous avons pris la référence appelée strLocal
et l' avons dirigée vers une toute nouvelle chaîne. Qu'arrive-t-il strMain
lorsque nous faisons cela? 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"
Changeons le scénario une seconde. Imaginez que nous ne travaillons pas avec des chaînes, 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 en a encore qu'un MutableThing
en mémoire, et la référence copiée et la référence d'origine pointent toujours vers elle. Les propriétés de MutableThing
lui-même 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 cordes sont immuables! Il n'y a aucune ChangeMe
propriété à définir. Vous ne pouvez pas faire strLocal[3] = 'H'
en C # comme vous pourriez le faire avec un char
tableau de style C ; vous devez plutôt construire une toute nouvelle chaîne. La seule façon de changer strLocal
est de pointer la référence sur une autre chaîne, et cela signifie que rien de ce que vous faites ne strLocal
peut affecter strMain
. La valeur est immuable et la référence est une copie.
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 dans est Main
vraiment modifiée parce que vous avez passé la référence sans la copier sur la pile.
Ainsi, même si les chaînes sont des types de référence, les passer par valeur signifie que tout ce qui se passe dans l'appelé n'affectera pas la chaîne de l'appelant. Mais comme ce sont des types de référence, vous n'avez pas à copier la chaîne entière en mémoire lorsque vous souhaitez la transmettre.
ref
mot clé. Pour prouver que le passage par référence fait une différence, regardez cette démo: rextester.com/WKBG5978
ref
mot - clé a une utilité, j'essayais juste d'expliquer pourquoi on pourrait penser que passer un type de référence par valeur en C # ressemble à la notion "traditionnelle" (c'est-à-dire C) de passer par référence (et de passer un type de référence par référence en C # ressemble plus à passer une référence à une référence par valeur).
Foo(string bar)
pourrait être considéré comme Foo(char* bar)
alors Foo(ref string bar)
serait Foo(char** bar)
(ou Foo(char*& bar)
ou Foo(string& bar)
en C ++). Bien sûr, ce n'est pas ainsi que vous devriez y penser tous les jours, mais cela m'a en fait aidé à comprendre enfin ce qui se passe sous le capot.
Les chaînes en C # sont des objets de référence immuables. Cela signifie que les références à celles-ci 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 découpées, etc.) créent des copies modifiées de la chaîne d'origine.
Les chaînes 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 passée à votre fonction, mais lorsque la chaîne est modifiée, elle devient une nouvelle instance et ne modifie pas l'ancienne instance.
Uri
(class) et Guid
(struct) sont également des cas spéciaux. Je ne vois pas comment se System.String
comporte comme un "type valeur", pas plus que d'autres types immuables ... d'origines de classe ou de structure.
Uri
& Guid
- vous pouvez simplement affecter une valeur littérale de chaîne à une variable de chaîne. La chaîne semble être modifiable, comme une int
réaffectation, mais elle crée un objet implicitement - pas de new
mot clé.