Liste passée par ref - aidez-moi à expliquer ce comportement

109

Jetez un œil au programme suivant:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

Je supposais que myListserait passé ref, et la sortie serait

3
4

La liste est bien "passée par ref", mais seule la sortfonction prend effet. La déclaration suivante myList = myList2;n'a aucun effet.

Donc, la sortie est en fait:

10
50
100

Pouvez-vous m'aider à expliquer ce comportement? Si en effet myListn'est pas passé par référence (comme il semble myList = myList2ne pas avoir d'effet), comment myList.Sort()prend-il effet?

Je supposais même que cette déclaration ne prenait pas effet et que la sortie était:

100
50
10
nmdr
la source
Juste une observation (et je me rends compte que le problème a été simplifié ici), mais il semble qu'il serait préférable ChangeListde renvoyer un List<int>plutôt que d'être un voids'il s'agit en fait de créer une nouvelle liste.
Jeff B

Réponses:

110

Vous passez une référence à la liste , mais vous ne transmettez pas la variable de liste par référence - donc lorsque vous appelez ChangeListla valeur de la variable (c'est-à-dire la référence - pensez "pointeur") est copiée - et change la valeur du les paramètres à l' intérieur ChangeList ne sont pas vus par TestMethod.

essayer:

private void ChangeList(ref List<int> myList) {...}
...
ChangeList(ref myList);

Cela passe ensuite une référence à la variable locale myRef (comme déclaré dans TestMethod); maintenant, si vous réaffectez le paramètre à l'intérieur, ChangeListvous réaffectez également la variable à l' intérieur TestMethod .

Marc Gravell
la source
En effet, je peux le faire, mais je veux savoir comment le tri prend effet
nmdr
6
@Ngm - lorsque vous appelez ChangeList, seule la référence est copiée - c'est le même objet. Si vous modifiez l'objet d'une manière ou d'une autre, tout ce qui a une référence à cet objet verra le changement.
Marc Gravell
225

Initialement, il peut être représenté graphiquement comme suit:

États init

Ensuite, le tri est appliqué myList.Sort(); Trier la collection

Enfin, lorsque vous avez fait:, myList' = myList2vous avez perdu celui de la référence mais pas l'original et la collection est restée triée.

Référence perdue

Si vous utilisez par référence ( ref) alors myList'et myListdeviendra le même (une seule référence).

Remarque: j'utilise myList'pour représenter le paramètre que vous utilisez dans ChangeList(car vous avez donné le même nom que l'original)

Jaider
la source
20

Voici un moyen simple de le comprendre

  • Votre liste est un objet créé sur le tas. La variable myListest une référence à cet objet.

  • En C #, vous ne passez jamais d'objets, vous passez leurs références par valeur.

  • Lorsque vous accédez à l'objet de liste via la référence passée dans ChangeList(lors du tri, par exemple), la liste d'origine est modifiée.

  • L'affectation sur la ChangeListméthode est faite à la valeur de la référence, donc aucune modification n'est apportée à la liste d'origine (toujours sur le tas mais plus référencée sur la variable de méthode).

Unmesh Kondolikar
la source
10

Ce lien vous aidera à comprendre le passage par référence en C #. Fondamentalement, lorsqu'un objet de type référence est passé par valeur à une méthode, seules les méthodes disponibles sur cet objet peuvent modifier le contenu de l'objet.

Par exemple, la méthode List.sort () change le contenu de la liste mais si vous affectez un autre objet à la même variable, cette affectation est locale à cette méthode. C'est pourquoi myList reste inchangé.

Si nous transmettons un objet de type référence en utilisant le mot-clé ref, nous pouvons attribuer un autre objet à la même variable et cela change tout l'objet lui-même.

(Modifier: il s'agit de la version mise à jour de la documentation liée ci-dessus.)

Shekhar
la source
5

C # fait juste une copie superficielle quand il passe par valeur à moins que l'objet en question ne s'exécute ICloneable(ce que la Listclasse ne fait apparemment pas).

Cela signifie qu'il copie Listlui - même, mais les références aux objets à l'intérieur de la liste restent les mêmes; autrement dit, les pointeurs continuent à référencer les mêmes objets que l'original List.

Si vous modifiez les valeurs des choses que vos nouvelles Listréférences, vous modifiez Listégalement l'original (puisqu'il fait référence aux mêmes objets). Cependant, vous modifiez ensuite ce qui fait myListréférence entièrement à un nouveau List, et maintenant seul l'original Listfait référence à ces entiers.

Lisez la section Passing Reference-Type Parameters de cet article MSDN sur «Passing Parameters» pour plus d'informations.

"Comment cloner une liste générique en C #" de StackOverflow explique comment créer une copie complète d'une liste.

Ethel Evans
la source
3

Bien que je sois d'accord avec ce que tout le monde a dit ci-dessus. J'ai une vision différente de ce code. En gros, vous affectez la nouvelle liste à la variable locale myList et non à la variable globale. si vous changez la signature de ChangeList (List myList) en private void ChangeList (), vous verrez la sortie de 3, 4.

Voici mon raisonnement ... Même si la liste est passée par référence, pensez-y comme en passant une variable de pointeur par valeur Lorsque vous appelez ChangeList (myList), vous passez le pointeur à (Global) myList. Maintenant, cela est stocké dans la variable (locale) myList. Alors maintenant, votre (locale) myList et (globale) myList pointent vers la même liste. Maintenant, vous faites un tri => cela fonctionne car (local) myList fait référence à la myList d'origine (globale) Ensuite, vous créez une nouvelle liste et assignez le pointeur à votre (locale) myList. Mais dès que la fonction sort, la variable (locale) myList est détruite. HTH

class Test
{
    List<int> myList = new List<int>();
    public void TestMethod()
    {

        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList();

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList()
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}
sandeep
la source
2

Utilisez le refmot - clé.

Regardez la référence définitive ici pour comprendre les paramètres de passage.
Pour être précis, regardez ceci , pour comprendre le comportement du code.

EDIT: Sortfonctionne sur la même référence (qui est passée par valeur) et donc les valeurs sont ordonnées. Cependant, l'affectation d'une nouvelle instance au paramètre ne fonctionnera pas car le paramètre est passé par valeur, sauf si vous mettez ref.

Putting refvous permet de changer le pointeur vers la référence à une nouvelle instance de Listdans votre cas. Sans ref, vous pouvez travailler sur le paramètre existant, mais vous ne pouvez pas le faire pointer vers autre chose.

shahkalpesh
la source
0

Il y a deux parties de mémoire allouées pour un objet de type référence. Un en pile et un en tas. La partie dans la pile (aka un pointeur) contient une référence à la partie dans le tas - où les valeurs réelles sont stockées.

Lorsque le mot clé ref n'est pas utilisé, une seule copie de la pièce dans la pile est créée et transmise à la méthode - référence à la même pièce dans le tas. Par conséquent, si vous modifiez quelque chose en tas, ces changements seront conservés. Si vous modifiez le pointeur copié - en l'affectant pour qu'il fasse référence à un autre endroit dans le tas - cela n'affectera pas le pointeur d'origine en dehors de la méthode.

Thinh Tran
la source