Liste von ref übergeben - helfen Sie mir, dieses Verhalten zu erklären

109

Schauen Sie sich folgendes Programm an:

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

Ich nahm an myList, dass vorbei refgegangen wäre, und die Ausgabe würde

3
4

Die Liste wird zwar "von ref übergeben", aber nur die sortFunktion wird wirksam. Die folgende Aussage myList = myList2;hat keine Wirkung.

Die Ausgabe ist also tatsächlich:

10
50
100

Können Sie mir helfen, dieses Verhalten zu erklären? Wenn es tatsächlich myListnicht von ref übergeben wird (wie es scheint, wenn es myList = myList2nicht wirksam wird), wie wird myList.Sort()es wirksam?

Ich ging davon aus, dass selbst diese Aussage nicht wirksam wird und die Ausgabe wie folgt lautet:

100
50
10
nmdr
quelle
Nur eine Beobachtung (und mir ist klar, dass das Problem hier vereinfacht wurde), aber es scheint, als wäre es am besten ChangeList, eine zurückzugeben, List<int>anstatt eine zu sein, voidwenn tatsächlich eine neue Liste erstellt wird.
Jeff B

Antworten:

110

Sie übergeben einen Verweis auf die Liste , aber Sie übergeben die Listenvariable nicht als Referenz. Wenn Sie also ChangeListden Wert der Variablen aufrufen (dh die Referenz - denken Sie an "Zeiger"), wird kopiert - und ändert sich in den Wert des Parameter im Inneren ChangeList werden von nicht gesehen TestMethod.

Versuchen:

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

Dies übergibt dann einen Verweis auf die lokale Variable myRef (wie in deklariert TestMethod); Wenn Sie jetzt den Parameter im Inneren neu ChangeListzuweisen, weisen Sie auch die Variable im Inneren neu zu TestMethod .

Marc Gravell
quelle
Ich kann das zwar, aber ich möchte wissen, wie die Sortierung wirkt
nmdr
6
@Ngm - Wenn Sie aufrufen ChangeList, wird nur die Referenz kopiert - es ist dasselbe Objekt. Wenn Sie das Objekt auf irgendeine Weise ändern, wird die Änderung für alles angezeigt , das auf dieses Objekt verweist.
Marc Gravell
224

Zunächst kann es wie folgt grafisch dargestellt werden:

Init Zustände

Dann wird die Sortierung angewendet myList.Sort(); Sammlung sortieren

Als Sie das getan haben myList' = myList2, haben Sie schließlich die Referenz verloren, aber nicht das Original, und die Sammlung blieb sortiert.

Referenz verloren

Wenn Sie als Referenz ( ref) verwenden, wird myList'und myListwird dieselbe (nur eine Referenz).

Hinweis: Ich verwende myList', um den Parameter darzustellen, in dem Sie verwenden ChangeList(weil Sie den gleichen Namen wie das Original angegeben haben).

Jaider
quelle
20

Hier ist ein einfacher Weg, es zu verstehen

  • Ihre Liste ist ein Objekt, das auf einem Heap erstellt wurde. Die Variable myListist eine Referenz auf dieses Objekt.

  • In C # übergeben Sie niemals Objekte, sondern deren Referenzen als Wert.

  • Wenn Sie über die übergebene Referenz in ChangeList(z. B. beim Sortieren) auf das Listenobjekt zugreifen , wird die ursprüngliche Liste geändert.

  • Die Zuweisung für die ChangeListMethode erfolgt zum Wert der Referenz, daher werden keine Änderungen an der ursprünglichen Liste vorgenommen (immer noch auf dem Heap, aber nicht mehr auf der Methodenvariablen referenziert).

Unmesh Kondolikar
quelle
10

Dieser Link hilft Ihnen beim Verständnis der Referenzübergabe in C #. Wenn ein Objekt vom Referenztyp als Wert an eine Methode übergeben wird, können grundsätzlich nur Methoden, die für dieses Objekt verfügbar sind, den Inhalt des Objekts ändern.

Beispielsweise ändert die List.sort () -Methode den Listeninhalt. Wenn Sie jedoch derselben Variablen ein anderes Objekt zuweisen, ist diese Zuweisung für diese Methode lokal. Deshalb bleibt myList unverändert.

Wenn wir ein Objekt vom Referenztyp mit dem Schlüsselwort ref übergeben, können wir derselben Variablen ein anderes Objekt zuweisen, das das gesamte Objekt selbst ändert.

(Bearbeiten: Dies ist die aktualisierte Version der oben verlinkten Dokumentation.)

Shekhar
quelle
5

C # erstellt nur eine flache Kopie, wenn der Wert übergeben wird, es sei denn, das betreffende Objekt wird ausgeführt ICloneable(was anscheinend die ListKlasse nicht tut).

Dies bedeutet, dass es das Listselbst kopiert , die Verweise auf die Objekte in der Liste jedoch gleich bleiben. Das heißt, die Zeiger verweisen weiterhin auf dieselben Objekte wie das Original List.

Wenn Sie die Werte der Dinge ändern, auf die Ihre neuen ListReferenzen verweisen, ändern Sie auch das Original List(da es auf dieselben Objekte verweist). Sie ändern dann jedoch die myListVerweise vollständig auf eine neue List, und jetzt Listverweist nur das Original auf diese Ganzzahlen.

Weitere Informationen finden Sie im Abschnitt Übergeben von Referenztypparametern in diesem MSDN-Artikel unter "Übergeben von Parametern" .

"Wie klone ich eine generische Liste in C # ? " Von StackOverflow beschreibt, wie eine tiefe Kopie einer Liste erstellt wird.

Ethel Evans
quelle
3

Ich stimme zwar dem zu, was alle oben gesagt haben. Ich habe eine andere Sicht auf diesen Code. Grundsätzlich weisen Sie die neue Liste der lokalen Variablen myList zu, nicht der globalen. Wenn Sie die Signatur von ChangeList (List myList) in private void ChangeList () ändern, wird die Ausgabe von 3, 4 angezeigt.

Hier ist meine Argumentation ... Auch wenn die Liste als Referenz übergeben wird, stellen Sie sich vor, Sie übergeben eine Zeigervariable als Wert. Wenn Sie ChangeList (myList) aufrufen, übergeben Sie den Zeiger an (Global) myList. Dies wird nun in der (lokalen) myList-Variablen gespeichert. Jetzt zeigen Ihre (lokale) myList und (globale) myList auf dieselbe Liste. Jetzt sortieren Sie => es funktioniert, weil (lokale) myList auf die ursprüngliche (globale) myList verweist. Als Nächstes erstellen Sie eine neue Liste und weisen den Zeiger Ihrer (lokalen) myList zu. Sobald die Funktion beendet wird, wird die (lokale) myList-Variable zerstört. 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
quelle
2

Verwenden Sie das refSchlüsselwort.

Schauen Sie sich hier die endgültige Referenz an , um die Übergabeparameter zu verstehen.
Um genau zu sein, Blick auf diesem , um das Verhalten des Codes zu verstehen.

BEARBEITEN: Sortarbeitet mit derselben Referenz (die als Wert übergeben wird) und daher werden die Werte geordnet. Das Zuweisen einer neuen Instanz zum Parameter funktioniert jedoch nicht, da der Parameter als Wert übergeben wird, es sei denn, Sie geben ein ref.

Mit Putting refkönnen Sie den Zeiger auf den Verweis auf eine neue Instanz von Listin Ihrem Fall ändern . Ohne refkönnen Sie an dem vorhandenen Parameter arbeiten, aber nicht auf etwas anderes verweisen.

shahkalpesh
quelle
0

Für ein Objekt vom Referenztyp sind zwei Teile des Speichers zugeordnet. Einer im Stapel und einer im Haufen. Das Teil im Stapel (auch als Zeiger bezeichnet) enthält einen Verweis auf das Teil im Heap, in dem die tatsächlichen Werte gespeichert sind.

Wenn das Schlüsselwort ref nicht verwendet wird, wird nur eine Kopie des Teils im Stapel erstellt und an die Methode übergeben - Verweis auf dasselbe Teil im Heap. Wenn Sie also etwas im Heap-Teil ändern, bleiben diese Änderungen erhalten. Wenn Sie den kopierten Zeiger ändern, indem Sie ihn so zuweisen, dass er auf eine andere Stelle im Heap verweist, wirkt sich dies nicht auf den Ursprungszeiger außerhalb der Methode aus.

Thinh Tran
quelle