195 votes

Type de référence de chaîne C# ?

Je sais que la "chaîne" en C# est un type de référence. Cela se trouve sur MSDN. Cependant, ce code ne fonctionne pas comme il devrait alors :

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

La sortie devrait être "before passing" "after passing" puisque je passe la chaîne en tant que paramètre et qu'il s'agit d'un type de référence, la deuxième instruction de sortie devrait reconnaître que le texte a changé dans la méthode TestI. Cependant, j'obtiens "before passing" "before passing", ce qui donne l'impression que le texte est passé par valeur et non par référence. Je comprends que les chaînes de caractères sont immuables, mais je ne vois pas comment cela pourrait expliquer ce qui se passe ici. Qu'est-ce qui m'échappe ? Merci.

244voto

Jon Skeet Points 692016

La référence à la chaîne est passée par valeur. Il y a une grande différence entre passer une référence par valeur et passer un objet par référence. Il est regrettable que le mot "référence" soit utilisé dans les deux cas.

Si vous faire passer la référence de la chaîne par référence, il fonctionnera comme vous le souhaitez :

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

Vous devez maintenant faire la distinction entre les modifications apportées à l'objet auquel une référence fait référence et les modifications apportées à une variable (comme un paramètre) pour qu'elle fasse référence à un objet différent. Nous ne pouvons pas modifier une chaîne de caractères parce que les chaînes de caractères sont immuables, mais nous pouvons le démontrer à l'aide d'un paramètre StringBuilder à la place :

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

Voir mon article sur le passage des paramètres pour plus de détails.

41voto

Martin Dimitrov Points 1843

Si nous devons répondre à la question : String est un type de référence et il se comporte comme une référence. Nous passons un paramètre qui contient une référence à, et non la chaîne réelle. Le problème est dans la fonction :

public static void TestI(string test)
{
    test = "after passing";
}

Le paramètre test contient une référence à la chaîne de caractères mais c'est une copie. Nous avons deux variables qui pointent vers la chaîne. Et comme toute opération sur les chaînes de caractères crée un nouvel objet, nous faisons en sorte que notre copie locale pointe sur la nouvelle chaîne. Mais l'original test n'est pas modifiée.

Les solutions proposées pour mettre ref dans la déclaration de la fonction et dans l'invocation fonctionnent parce que nous ne transmettrons pas la valeur de l'élément test mais ne transmettra qu'une référence à cette variable. Ainsi, tout changement à l'intérieur de la fonction reflétera la variable originale.

Je veux répéter à la fin : String est un type de référence mais comme il est immuable la ligne test = "after passing"; crée en fait un nouvel objet et une copie de la variable test est modifié pour pointer vers la nouvelle chaîne.

29voto

Derek W Points 2848

Comme d'autres l'ont dit, le String dans .NET est immuable et sa référence est transmise par valeur.

Dans le code original, dès que cette ligne est exécutée :

test = "after passing";

puis test n'est plus référant à l'objet original. Nous avons créé un nouveau String et affecté test pour référencer cet objet sur le tas géré.

J'ai l'impression que beaucoup de gens se trompent ici car il n'y a pas de constructeur formel visible pour leur rappeler. Dans ce cas, cela se passe dans les coulisses puisque le constructeur String Le type a un support linguistique dans la manière dont il est construit.

C'est pourquoi le passage à test n'est pas visible en dehors de la portée de la TestI(string) nous avons transmis la référence par valeur et maintenant cette valeur a changé ! Mais si la méthode String ont été transmises par référence, alors lorsque la référence a changé, nous le verrons en dehors de la portée de l'objet TestI(string) méthode.

Soit le réf. ou out mot-clé sont nécessaires dans ce cas. Je pense que le out pourrait être légèrement mieux adapté à cette situation particulière.

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}

9voto

eglasius Points 26221

En fait, cela aurait été la même chose pour n'importe quel objet d'ailleurs, c'est-à-dire qu'être un type de référence et passer par référence sont deux choses différentes en C#.

Cela fonctionnerait, mais cela s'applique quel que soit le type :

public static void TestI(ref string test)

De même, la chaîne de caractères est un type de référence, mais c'est aussi un type spécial. Il est conçu pour être immuable, de sorte que toutes ses méthodes ne modifient pas l'instance (elles en renvoient une nouvelle). Il comporte également des éléments supplémentaires pour améliorer les performances.

8voto

Bryan Points 186

Voici une bonne façon de penser à la différence entre les types de valeur, le passage par valeur, les types de référence et le passage par référence :

Une variable est un conteneur.

Une variable de type valeur contient une instance. Une variable de type référence contient un pointeur vers une instance stockée ailleurs.

La modification d'une variable de type valeur entraîne la mutation de l'instance qu'elle contient. La modification d'une variable de type référence entraîne la mutation de l'instance vers laquelle elle pointe.

Des variables de type référence distinctes peuvent pointer vers la même instance. Par conséquent, la même instance peut être mutée via n'importe quelle variable qui pointe vers elle.

Un argument passé par valeur est un nouveau conteneur avec une nouvelle copie du contenu. Un argument passé par référence est le conteneur original avec son contenu original.

Lorsqu'un argument de type valeur est transmis par valeur : La réaffectation du contenu de l'argument n'a aucun effet hors de la portée, car le conteneur est unique. La modification de l'argument n'a aucun effet en dehors de la portée, car l'instance est une copie indépendante.

Lorsqu'un argument de type référence est passé par valeur : La réaffectation du contenu de l'argument n'a aucun effet en dehors de la portée, car le conteneur est unique. Modifier le contenu de l'argument affecte la portée externe, car le pointeur copié pointe vers une instance partagée.

Quand un argument est passé par référence : La réaffectation du contenu de l'argument affecte la portée externe, car le conteneur est partagé. Modifier le contenu de l'argument affecte la portée externe, car le contenu est partagé.

En conclusion :

Une variable de type chaîne est une variable de type référence. Elle contient donc un pointeur vers une instance stockée ailleurs. Lorsqu'elle est passée par valeur, son pointeur est copié, de sorte que la modification d'un argument de type chaîne devrait affecter l'instance partagée. Cependant, une instance de chaîne n'a pas de propriétés mutables, donc un argument de chaîne ne peut pas être modifié de toute façon. Lorsqu'il est passé par référence, le conteneur du pointeur est partagé, donc la réaffectation affectera toujours la portée externe.

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