2016-08-01 12 views
0

ich ein Problem mit der Verwendung von C#, wenn ich eine bestimmte Liste initialisieren können, sagen List<T> exampleList andere vorbestehende Liste verwenden, können toModify sagen wie folgt aus: List<T> exampleList = new List<T>(toModify). Wenn ich später modifiziere, um die Liste zu ändern, ändert sich die neu erstellte Liste auch selbst. Wenn es den Wert als Referenz übergibt sollte nicht der Wert von exampleList gleich bleiben, da es von der anderen generiert wurde?eine neue Instanz einer Liste in C# generieren

TLDR: Wert einer Liste, die ich mit einer anderen Liste (zweite Liste) initialisiere, ändert sich, wenn ich die zweite Liste ändere. Ich komme aus einem Java-Hintergrund und kann nicht verstehen, warum das passiert. Muss ich immer Klon verwenden?

+5

Das Verhalten von C# ist genau dasselbe wie das Verhalten von Java in dieser Hinsicht. Können Sie erklären, was Sie meinen, wenn Sie sagen, dass Sie die Liste "modifizieren"? Stellen Sie vielleicht ein minimales Codebeispiel zur Verfügung, um das Verhalten zu zeigen, das Sie sehen? – StriplingWarrior

+0

[C# Konzepte: Wert gegen Referenztypen] (http://www.albahari.com/valuevsreftypes.aspx) –

Antwort

1

Nehmen wir ein Beispiel verwenden:

 List<A> firstList = new List<A>() 
     { 
      new A() { Id = 3 }, 
      new A() { Id = 5 } 
     }; 

     List<A> secondList = new List<A>(firstList); 

     secondList[1].Id = 999; 

     Console.WriteLine(firstList[1].Id); 

Ausgang: 999

Der Hauptgrund für Obwohl wir ein neues List<T> Objekt erstellen (yes collection is object), das auf einen neuen Speicher verweist, der im verwalteten Heap zugewiesen wurde, funktioniert es aber immer noch mit Referenzen auf gleiche Objekte. (!)

Liste der neu erstellen Objekte mit gleichen Werten Sie müssen irgendwie Elemente klonen, ist eine Möglichkeit, LINQ .Select() Methode zu verwenden, um neue Objekte und ToList() Methode erstellen, um die Umrechnung zur Liste:

 List<A> firstList = new List<A>() 
     { 
      new A() { Id = 3 }, 
      new A() { Id = 5 } 
     }; 

     List<A> secondList = firstList.Select(el => new A() { Id = el.Id }).ToList(); 

     secondList[1].Id = 999; 

     Console.WriteLine(firstList[1].Id); 

Ausgabe: 5

+0

Wie kann ich das Objekt den Verweis auf einen anderen Speicherblock kopieren und nur diesen Block ändern? –

+0

Hmmmm, was meinst du mit dieser Zeile: 'Objekt kopieren Verweis auf einen anderen Speicherblock'? – Fabjan

+0

Anstatt nur den neuen Objektpunkt zu erstellen oder auf den ersten Bezugspunkt zu verweisen, wie kann ich eine exakte Kopie dieser Referenz erstellen und sie an einer anderen Stelle platzieren, so dass beim Ändern des zweiten Objekts die erste nicht geändert wird? –

3

Ja.

Sie erstellen eine neue Liste mit den gleichen Elementen wie die alte Liste. Wenn Sie die erste Liste löschen, bleiben die Elemente in der zweiten Liste erhalten.

Wenn Sie jedoch eine Eigenschaft für eines der Elemente in der ersten Liste ändern, ist es das gleiche Objekt in der zweiten Liste.

Also referenzieren beide Listen die gleichen Elemente im Speicher. Wenn Sie list1[0].SomeProperty = 1 schreiben, ändern Sie das unter Verwendung der Objektreferenz, die in list2 identisch ist, so dass Änderungen in der zweiten Liste wiedergegeben werden.

Für das Klonen einer Liste und das Generieren neuer Referenzen für Elemente, überprüfen Sie diese SO Answer.

+0

Warum machen sie keine Funktion für die Schnittstelle Liste, so dass Sie es kopieren können, ohne dass das passiert? Mit anderen Worten, ändern Sie die Speicherreferenz, indem Sie eine neue erstellen, ohne dass Sie eine Klonmethode erstellen müssen, um den Job auszuführen? –

+0

@TiPiTo Das Kopieren eines Objekts ist komplexer, als Sie es sich vorstellen. Sie haben möglicherweise eine Hierarchie von Unterobjekten und komplexen Eigenschaften. Google "Deep Copy" in C#. – user3185569

+0

Wäre eine tiefe Kopie nicht sehr ineffizient, um das Objekt zu "klonen", da es jeden Index durchläuft, anstatt nur den Speicherblock zu kopieren? –

2

In der folgenden Zeile:

List<T> exampleList = new List<T>(toModify) 

erstellen Sie eine Liste von T List<T> ‚s Konstruktor aufrufen, die IEnumerable<T> ein Argument vom Typ nimmt. Weitere Informationen zu letzterem finden Sie unter .

Die Argumente der Methode in C# werden standardmäßig nach Wert und nicht per Referenz übergeben. Sie können als Referenz übergeben werden, aber Sie müssen dies explizit in der Signatur der entsprechenden Methode angeben, indem Sie das Schlüsselwort ref verwenden und an dem Punkt, an dem Sie diese Methode aufrufen, erneut dasselbe Schlüsselwort verwenden. So wird der toModify Wert an den Konstruktor List<T> übergeben.

Was ist das wichtig?

In C# Typen können (trotz der Tatsache, dass alle Arten von System.Object erben) in zwei Kategorien unterteilt werden:

  • Werttypen
  • Referenztypen

Wenn wir passieren ein Werttyp als Argument übergeben wir eine Kopie des Wertes. Jede Änderung, die wir entweder im ursprünglichen Wert oder in der Kopie des ursprünglichen Werts vornehmen, wird nicht zueinander widergespiegelt. Auf der anderen Seite, wenn wir einen Referenztyp als Argument übergeben, übergeben wir eine Kopie dieser Referenz. Jetzt haben wir zwei Referenzen (Zeiger), die auf den gleichen Ort im Speicher zeigen. Wenn wir jedoch eine Eigenschaft des Objekts ändern, auf die beide Referenzen zeigen, ist dies für beide sichtbar.

In Ihrem Fall ist dies, was passiert.toModify ist eine Liste von Referenztypen (unter der Haube haben Sie ein Array, dessen Elemente Verweise auf andere Objekte sind). Jede Änderung an den Elementen der ursprünglichen Liste, toModify, spiegelt sich in der Liste wider, die Sie anhand dieser Liste erstellen.

Ein einfaches Beispiel, dass Sie die oben zu überprüfen, verwenden könnte, ist die folgende:

public class Point 
{ 
    public int X { get; set; } 
    public int Y { get; set; } 

    public override string ToString() => $"X: {X}, Y: {Y}"; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var listA = new List<int> {1, 2, 3}; 
     var listB = new List<int>(listA); 
     // Before the modification 
     Console.WriteLine(listA[0]); // prints 1 
     Console.WriteLine(listB[0]); // prints 1 
     listA[0] = 2; 
     // After the mofication 
     Console.WriteLine(listA[0]); // prints 2 
     Console.WriteLine(listB[0]); // prints 1 
     Console.ReadKey(); 

     var pointsA = new List<Point> 
     { 
      new Point {X = 3, Y = 4}, 
      new Point {X = 4, Y = 5}, 
      new Point {X = 6, Y = 8}, 
     }; 
     var pointsB = new List<Point>(pointsA); 
     // Before the modification 
     Console.WriteLine(pointsA[0]); // prints X: 3, Y: 4 
     Console.WriteLine(pointsB[0]); // prints X: 3, Y: 4 
     pointsA[0].X = 4; 
     pointsA[0].Y = 3; 
     // After the modification 
     Console.WriteLine(pointsA[0]); // prints X: 4, Y: 3 
     Console.WriteLine(pointsB[0]); // prints X: 4, Y: 3 

     Console.ReadKey(); 
    } 
} 
+0

Wie kann ich einen Referenztyp nach Wert übergeben, ohne die Klonmethode zu verwenden? –

Verwandte Themen