2009-07-08 7 views
137

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

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 « avant de passer » « après le passage », car je passe la chaîne comme un paramètre et étant un type de référence, La deuxième déclaration de sortie doit reconnaître que le texte a changé dans la méthode TestI. Cependant, je reçois "avant de passer" "avant de passer" en faisant croire qu'il est passé par valeur et non par ref. Je comprends que les chaînes sont immuables, mais je ne vois pas comment cela pourrait expliquer ce qui se passe ici. Qu'est-ce que je rate? Merci.

+0

Voir l'article référencé par Jon ci-dessous. Le comportement que vous mentionnez peut également être reproduit par des pointeurs C++. – Sesh

+0

Très belle explication dans [MSDN] (http://msdn.microsoft.com/en-us/library/s6938f28.aspx) aussi. –

+0

Copie possible de [En C#, pourquoi String est un type de référence qui se comporte comme un type de valeur?] (Http://stackoverflow.com/questions/636932/in-c-why-is-string-a-reference-type -that-like-like-a-value-type) – Liam

Répondre

185

La référence à la chaîne est transmise 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 faites passer la référence de chaîne par référence, il fonctionnera comme prévu:

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"; 
    } 
} 

Maintenant, vous devez faire la distinction entre faire des changements à l'objet une référence fait référence, et faire une modification à une variable (comme un paramètre) pour le laisser faire référence à un objet différent. Nous ne pouvons pas modifier une chaîne parce que les chaînes sont immuables, mais nous pouvons le démontrer avec une place StringBuilder:

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 my article on parameter passing pour plus de détails.

+2

d'accord, je veux simplement préciser que l'utilisation du modificateur ref fonctionne également pour les types non référentiels, c'est-à-dire que les deux sont des concepts assez distincts. – eglasius

+2

@Jon Skeet a adoré le sidenote dans votre article. Vous devriez avoir 'référencé 'comme réponse –

0

Essayez:


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

Votre réponse devrait contenir plus que du code. Il devrait également contenir une explication de pourquoi cela fonctionne. –

9

En fait, il aurait été le même pour tout objet pour cette question à savoir être un type de référence et le passage par référence sont 2 choses différentes dans C#.

Cela pourrait fonctionner, mais qui applique quel que soit le type:

public static void TestI(ref string test) 

également au sujet de la chaîne étant un type de référence, est aussi un spécial. Son conçu pour être immuable, donc toutes ses méthodes ne modifieront pas l'instance (elles en retournent une nouvelle). Il a aussi des choses supplémentaires pour la performance.

25

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 à, pas 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, mais il est une copie. Nous avons deux variables pointant vers la chaîne. Et parce que toutes les opérations avec des chaînes créent réellement un nouvel objet, nous faisons notre copie locale pour pointer vers la nouvelle chaîne. Mais la variable originale test n'est pas modifiée.

Les solutions suggérées pour mettre ref dans la déclaration de fonction et dans le travail d'invocation parce que nous ne transmettrons pas la valeur de la variable test mais lui passerons juste une référence. Ainsi, tout changement dans la fonction reflétera la variable d'origine.

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

0

Je crois que votre code est analogue à ce qui suit, et vous ne devriez pas avoir prévu la valeur d'avoir changé pour la même raison, il ne serait pas ici:

public static void Main() 
{ 
    StringWrapper testVariable = new StringWrapper("before passing"); 
    Console.WriteLine(testVariable); 
    TestI(testVariable); 
    Console.WriteLine(testVariable); 
} 

public static void TestI(StringWrapper testParameter) 
{ 
    testParameter = new StringWrapper("after passing"); 

    // this will change the object that testParameter is pointing/referring 
    // to but it doesn't change testVariable unless you use a reference 
    // parameter as indicated in other answers 
} 
6

est ici une bonne façon de penser à la différence entre les types de valeur, les valeurs de passage, les types de référence et les références de passage:

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 fait muter l'instance qu'elle contient. La modification d'une variable de type référence mute l'instance vers laquelle elle pointe.

Les variables de type référence séparées 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 transmis-par-valeur est un nouveau conteneur avec une nouvelle copie du contenu. Un argument transmis par référence est le conteneur d'origine avec son contenu d'origine.

Lorsqu'un argument de type valeur est transmis valeur 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. 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 transmis valeur 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. La modification du contenu de l'argument affecte la portée externe, car le pointeur copié pointe vers une instance partagée.

Lorsqu'un argument est transmis-par-référence: La réaffectation du contenu de l'argument affecte la portée externe, car le conteneur est partagé. La modification du contenu de l'argument affecte la portée externe, car le contenu est partagé.

En conclusion:

Une variable de chaîne est une variable de type de référence. Par conséquent, il contient un pointeur vers une instance stockée ailleurs. Lors de la transmission d'une valeur, son pointeur est copié. La modification d'un argument de chaîne doit donc affecter l'instance partagée. Cependant, une instance de chaîne n'a aucune propriété mutable, donc un argument de chaîne ne peut pas être modifié de toute façon. Lorsqu'il est transmis par référence, le conteneur du pointeur est partagé, de sorte que la réaffectation affecte toujours la portée externe.

18

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

Dans le code d'origine, dès que cette ligne exécute:

test = "after passing"; 

alors test n'est plus référence à l'objet d'origine. Nous avons créé un objet nouvelString et affecté test pour référencer cet objet sur le tas géré.

Je sens que beaucoup de gens se font trébucher ici car il n'y a pas de constructeur formel visible pour leur rappeler. Dans ce cas, il se passe dans les coulisses depuis le type String a le soutien de la langue dans la façon dont il est construit.

Par conséquent, c'est pourquoi la modification à test n'est pas visible en dehors de la portée de la méthode TestI(string) - nous avons passé la référence par valeur et maintenant que la valeur a changé! Mais si la référence String a été transmise par référence, alors lorsque la référence a été modifiée, nous la verrons hors de la portée de la méthode TestI(string).

Soit le ref ou sur mot-clé sont nécessaires dans ce cas. Je pense que le mot-clé 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"; 
    } 
} 
2

réponses ci-dessus sont utiles, je voudrais simplement ajouter un exemple que je pense est ce qui démontre clairement ce qui se passe quand on passe paramètre sans le mot-clé ref, même si ce paramètre est un type de référence:

MyClass c = new MyClass(); c.MyProperty = "foo"; 

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null 
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar 


private void CNull(MyClass c2) 
     {   
      c2 = null; 
     } 
private void CPropertyChange(MyClass c2) 
     { 
      c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well. 
     } 
Questions connexes