ToList () - Erstellt es eine neue Liste?

176

Nehmen wir an, ich habe eine Klasse

public class MyObject
{
   public int SimpleInt{get;set;}
}

Und ich habe eine List<MyObject>, und ich ToList()es und dann eine der ändern SimpleInt, wird meine Änderung wieder auf die ursprüngliche Liste weitergegeben. Mit anderen Worten, was wäre die Ausgabe der folgenden Methode?

public void RunChangeList()
{
  var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
  var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
  var objectList = objects.ToList();
  objectList[0].SimpleInt=5;
  return objects[0].SimpleInt;

}

Warum?

P / S: Es tut mir leid, wenn es offensichtlich erscheint, es herauszufinden. Aber ich habe jetzt keinen Compiler bei mir ...

Graviton
quelle
Eine Möglichkeit, es zu formulieren, besteht darin, .ToList()eine flache Kopie zu erstellen. Die Referenzen werden kopiert, aber die neuen Referenzen verweisen immer noch auf dieselben Instanzen wie die ursprünglichen Referenzen. Wenn Sie daran denken, ToListkönnen Sie keinen new MyObject()Zeitpunkt erstellen , an dem es sich um MyObjecteinen classTyp handelt.
Jeppe Stig Nielsen

Antworten:

217

Ja, es ToListwird eine neue Liste erstellt. Da es sich in diesem Fall jedoch MyObjectum einen Referenztyp handelt, enthält die neue Liste Verweise auf dieselben Objekte wie die ursprüngliche Liste.

Das Aktualisieren der SimpleIntEigenschaft eines Objekts, auf das in der neuen Liste verwiesen wird, wirkt sich auch auf das entsprechende Objekt in der ursprünglichen Liste aus.

(Wenn MyObjectals a structund nicht als deklariert wurde, classenthält die neue Liste Kopien der Elemente in der ursprünglichen Liste, und das Aktualisieren einer Eigenschaft eines Elements in der neuen Liste hat keine Auswirkungen auf das entsprechende Element in der ursprünglichen Liste.)

LukeH
quelle
1
Beachten Sie auch, dass mit einer Listder Strukturen eine Zuweisung wie objectList[0].SimpleInt=5nicht zulässig wäre (C # -Kompilierungsfehler ). Dies liegt daran, dass der Rückgabewert des getAccessors des Listenindexers keine Variable ist (es ist eine zurückgegebene Kopie eines Strukturwerts) und daher das Festlegen seines Mitglieds .SimpleIntmit einem Zuweisungsausdruck nicht zulässig ist (es würde eine Kopie mutieren, die nicht beibehalten wird). . Nun, wer benutzt schon veränderbare Strukturen?
Jeppe Stig Nielsen
2
@ Jeppe Stig Nielson: Ja. Und in ähnlicher Weise wird der Compiler Sie auch davon abhalten, solche Dinge zu tun foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Das wirklich böse Problem ist, wenn Sie etwas versuchen wie listOfStructs.ForEach(s => s.SimpleInt = 42): Der Compiler erlaubt es und der Code wird ohne Ausnahmen ausgeführt, aber die Strukturen in der Liste bleiben unverändert!
LukeH
62

Aus der Reflector'd-Quelle:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

Ja, Ihre ursprüngliche Liste wird nicht aktualisiert (dh Hinzufügungen oder Entfernungen), die referenzierten Objekte jedoch.

Chris S.
quelle
32

ToList Es wird immer eine neue Liste erstellt, die keine nachfolgenden Änderungen an der Sammlung widerspiegelt.

Es werden jedoch Änderungen an den Objekten selbst angezeigt (es sei denn, es handelt sich um veränderbare Strukturen).

Mit anderen Worten, wenn Sie ein Objekt in der ursprünglichen Liste durch ein anderes Objekt ersetzen, ToListenthält das Objekt weiterhin das erste Objekt.
Wenn Sie jedoch eines der Objekte in der ursprünglichen Liste ändern, ToListenthält das Objekt immer noch dasselbe (geänderte) Objekt.

SLaks
quelle
12

Die akzeptierte Antwort adressiert die Frage des OP anhand seines Beispiels korrekt. Sie gilt jedoch nur, wenn ToListsie auf eine konkrete Sammlung angewendet wird. Es gilt nicht, wenn die Elemente der Quellsequenz noch instanziiert werden müssen (aufgrund der verzögerten Ausführung). In letzterem Fall erhalten Sie möglicherweise bei jedem Anruf einen neuen Satz von ElementenToList (oder listen die Reihenfolge auf).

Hier ist eine Anpassung des OP-Codes, um dieses Verhalten zu demonstrieren:

public static void RunChangeList()
{
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
    var whatInt = ChangeToList(objs);   // whatInt gets 0
}

public static int ChangeToList(IEnumerable<MyObject> objects)
{
    var objectList = objects.ToList();
    objectList.First().SimpleInt = 5;
    return objects.First().SimpleInt;
}

Während der obige Code erfunden zu sein scheint, kann dieses Verhalten in anderen Szenarien als subtiler Fehler erscheinen. In meinem anderen Beispiel finden Sie eine Situation, in der Aufgaben wiederholt erzeugt werden.

Douglas
quelle
11

Ja, es wird eine neue Liste erstellt. Dies ist beabsichtigt.

Die Liste enthält dieselben Ergebnisse wie die ursprüngliche Aufzählungssequenz, wird jedoch in einer dauerhaften (speicherinternen) Sammlung zusammengefasst. Auf diese Weise können Sie die Ergebnisse mehrmals verwenden, ohne die Kosten für die Neuberechnung der Sequenz zu verursachen.

Das Schöne an LINQ-Sequenzen ist, dass sie zusammensetzbar sind. Oft IEnumerable<T>ist das Ergebnis der Kombination mehrerer Filter-, Bestell- und / oder Projektionsvorgänge. Erweiterungsmethoden wie ToList()und ToArray()ermöglichen es Ihnen, die berechnete Sequenz in eine Standardsammlung zu konvertieren.

LBushkin
quelle
6

Eine neue Liste wird erstellt, aber die darin enthaltenen Elemente verweisen auf die ursprünglichen Elemente (genau wie in der ursprünglichen Liste). Änderungen an der Liste selbst sind unabhängig, aber an den Elementen wird die Änderung in beiden Listen gefunden.

Preet Sangha
quelle
6

Stolpern Sie einfach über diesen alten Beitrag und denken Sie daran, meine zwei Cent hinzuzufügen. Im Zweifelsfall verwende ich im Allgemeinen schnell die GetHashCode () -Methode für jedes Objekt, um die Identitäten zu überprüfen. Also für oben -

    public class MyObject
{
    public int SimpleInt { get; set; }
}


class Program
{

    public static void RunChangeList()
    {
        var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
        Console.WriteLine("objs: {0}", objs.GetHashCode());
        Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
        var whatInt = ChangeToList(objs);
        Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
    }

    public static int ChangeToList(List<MyObject> objects)
    {
        Console.WriteLine("objects: {0}", objects.GetHashCode());
        Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
        var objectList = objects.ToList();
        Console.WriteLine("objectList: {0}", objectList.GetHashCode());
        Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
        objectList[0].SimpleInt = 5;
        return objects[0].SimpleInt;

    }

    private static void Main(string[] args)
    {
        RunChangeList();
        Console.ReadLine();
    }

Und antworte auf meiner Maschine -

  • Objekte: 45653674
  • objs [0]: 41149443
  • Objekte: 45653674
  • Objekte [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • whatInt: 5

Im Wesentlichen bleibt das Objekt, das die Liste enthält, im obigen Code dasselbe. Hoffe der Ansatz hilft.

Vibhu
quelle
4

Ich denke, das ist gleichbedeutend mit der Frage, ob ToList eine tiefe oder flache Kopie erstellt. Da ToList MyObject nicht klonen kann, muss es eine flache Kopie erstellen, sodass die erstellte Liste dieselben Referenzen wie die ursprüngliche enthält, sodass der Code 5 zurückgibt.

Timores
quelle
2

ToList erstellt eine brandneue Liste.

Wenn es sich bei den Elementen in der Liste um Werttypen handelt, werden sie direkt aktualisiert. Wenn es sich um Referenztypen handelt, werden alle Änderungen in den referenzierten Objekten wiedergegeben.

Oded
quelle
2

In dem Fall, in dem das Quellobjekt eine echte IEnumerable ist (dh nicht nur eine Sammlung, die als enumerable verpackt ist), gibt ToList () möglicherweise NICHT dieselben Objektreferenzen wie in der ursprünglichen IEnumerable zurück. Es wird eine neue Liste von Objekten zurückgegeben, aber diese Objekte sind möglicherweise nicht dieselben oder sogar gleich den Objekten, die von IEnumerable bei erneuter Aufzählung ausgegeben werden

prmph
quelle
1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

Dadurch wird auch das ursprüngliche Objekt aktualisiert. Die neue Liste enthält Verweise auf die darin enthaltenen Objekte, genau wie die ursprüngliche Liste. Sie können die Elemente entweder ändern und das Update wird im anderen angezeigt.

Wenn Sie nun eine Liste aktualisieren (Hinzufügen oder Löschen eines Elements), wird dies nicht in der anderen Liste angezeigt.

kemiller2002
quelle
1

Ich sehe nirgendwo in der Dokumentation, dass ToList () garantiert immer eine neue Liste zurückgibt. Wenn eine IEnumerable eine Liste ist, kann es effizienter sein, dies zu überprüfen und einfach dieselbe Liste zurückzugeben.

Die Sorge ist, dass Sie manchmal absolut sicher sein möchten, dass die zurückgegebene Liste! = Zur ursprünglichen Liste ist. Da Microsoft nicht dokumentiert, dass ToList eine neue Liste zurückgibt, können wir nicht sicher sein (es sei denn, jemand hat diese Dokumentation gefunden). Es könnte sich auch in Zukunft ändern, selbst wenn es jetzt funktioniert.

Neue Liste (IEnumerable enumerablestuff) gibt garantiert eine neue Liste zurück. Ich würde dies stattdessen verwenden.

Ben B.
quelle
2
In der Dokumentation hier heißt es: " Die ToList <TSource> (IEnumerable <TSource>) -Methode erzwingt eine sofortige Abfrageauswertung und gibt eine List <T> zurück, die die Abfrageergebnisse enthält. Sie können diese Methode an Ihre Abfrage anhängen, um sie zu erhalten eine zwischengespeicherte Kopie der Abfrageergebnisse. "Der zweite Satz wiederholt, dass Änderungen an der ursprünglichen Liste nach der Auswertung der Abfrage keine Auswirkungen auf die zurückgegebene Liste haben.
Raymond Chen
1
@RaymondChen Dies gilt für IEnumerables, jedoch nicht für Listen. Obwohl BenB ToListbeim Aufruf von a eine neue ListListenobjektreferenz zu erstellen scheint , hat er Recht, wenn er sagt, dass dies durch die MS-Dokumentation nicht garantiert wird.
Teejay
@RaymondChen Laut ChrisS-Quelle IEnumerable<>.ToList()wird tatsächlich als implementiert new List<>(source)und es gibt keine spezifische Überschreibung für List<>, sodass List<>.ToList()tatsächlich eine neue Listenobjektreferenz zurückgegeben wird . Aber auch hier gibt es laut MS-Dokumentation keine Garantie dafür, dass sich dies in Zukunft nicht ändert, obwohl es wahrscheinlich den größten Teil des Codes da draußen brechen wird.
Teejay