Tiefe Kopie von List

Ich versuche eine tiefe Kopie einer generischen Liste zu erstellen und frage mich, ob es andere Möglichkeiten gibt, die Kopiermethode zu erstellen und jedes Mitglied einzeln zu kopieren. Ich habe eine class, die ungefähr so ​​aussieht:

public class Data { private string comment; public string Comment { get { return comment; } set { comment = value; } } private List traceData; public List TraceData { get { return traceData; } set { traceData = value; } } } 

Und ich habe eine Liste der obigen Daten, dh List . Was ich versuche zu tun, ist eine Trace-Daten der Teilmenge von List auf eine Grafik, möglicherweise mit etwas Skalierung oder Sweeping auf die Daten. Ich muss natürlich nicht alles in der Liste darstellen, weil sie nicht in den Bildschirm passen.

Ich habe anfangs versucht, die Teilmenge der Liste mit der List.GetRange() -Methode zu erhalten, aber es scheint, dass die darunterliegende List flach statt tief kopiert wird. Wenn ich die Teilmenge wieder mit List.GetRange () erhalte, erhalte ich zuvor modifizierte Daten, nicht die Rohdaten, die an anderer Stelle abgerufen wurden.

Kann mir jemand eine Anleitung geben, wie ich das angehen soll? Danke vielmals.

Die idiomatische Methode, dies in C # zu erreichen, besteht darin, ICloneable auf Ihren Data zu implementieren und eine Clone Methode zu schreiben, die die Deep Copy-Methode Enumerable.CloneRange (und dann vermutlich eine Enumerable.CloneRange Methode, die einen Teil Ihrer Liste gleichzeitig klonen kann) jede eingebaute Trick- oder Framework-Methode, um es einfacher zu machen.

Sofern Speicher und performance kein echtes Problem darstellen, schlage ich vor, dass Sie sich bemühen, es so umzugestalten, dass es stattdessen mit unveränderlichen Datenobjekten arbeitet. Es wird viel einfacher enden.

Du kannst es versuchen

  public static object DeepCopy(object obj) { if (obj == null) return null; Type type = obj.GetType(); if (type.IsValueType || type == typeof(string)) { return obj; } else if (type.IsArray) { Type elementType = Type.GetType( type.FullName.Replace("[]", string.Empty)); var array = obj as Array; Array copied = Array.CreateInstance(elementType, array.Length); for (int i = 0; i < array.Length; i++) { copied.SetValue(DeepCopy(array.GetValue(i)), i); } return Convert.ChangeType(copied, obj.GetType()); } else if (type.IsClass) { object toret = Activator.CreateInstance(obj.GetType()); FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { object fieldValue = field.GetValue(obj); if (fieldValue == null) continue; field.SetValue(toret, DeepCopy(fieldValue)); } return toret; } else throw new ArgumentException("Unknown type"); } 

Danke an DetoX83 Artikel über Code-Projekt.

Wenn IClonable Weg für Sie zu schwierig ist. Ich schlage vor, zu etwas und zurück zu konvertieren. Es kann mit BinaryFormatter oder einem Json Converter wie Servicestack.Text gemacht werden, da es das schnellste in .Net ist.

Der Code sollte in etwa so aussehen:

 MyClass mc = new MyClass(); string json = mc.ToJson(); MyClass mcCloned = json.FromJson(); 

mcCloned wird nicht auf mc verweisen.

Am einfachsten (aber schmutzig) ist es, ICloneable von Ihrer class zu implementieren und die nächste Erweiterungsmethode zu verwenden:

 public static IEnumerable Clone(this IEnumerable collection) where T : ICloneable { return collection.Select(item => (T)item.Clone()); } 

Verwendung:

 var list = new List { new Data { Comment = "comment", TraceData = new List { 1, 2, 3 } }; var newList = list.Clone(); 

Eine andere Sache, die Sie tun können, ist Ihre class als serializable markieren und binäre Serialisierung verwenden. Hier ist ein funktionierendes Beispiel

  public class Program { [Serializable] public class Test { public int Id { get; set; } public Test() { } } public static void Main() { //create a list of 10 Test objects with Id's 0-10 List firstList = Enumerable.Range(0,10).Select( x => new Test { Id = x } ).ToList(); using (var stream = new System.IO.MemoryStream()) { var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); binaryFormatter.Serialize(stream, firstList); //serialize to stream stream.Position = 0; //deserialize from stream. List secondList = binaryFormatter.Deserialize(stream) as List; } Console.ReadKey(); } } 

Wenn Sie Ihre Objekte unveränderlich machen, brauchen Sie sich keine Sorgen zu machen, Kopien von ihnen herumzugeben, dann könnten Sie etwas tun wie:

 var toPlot = list.Where(d => d.ShouldBePlotted()); 

Da Ihre Sammlung änderbar ist, müssen Sie die Tiefenkopie programmgesteuert implementieren:

 public class Data { public string Comment { get; set; } public List TraceData { get; set; } public Data DeepCopy() { return new Data { Comment = this.Comment, TraceData = this.TraceData != null ? new List(this.TraceData) : null; } } } 

Das Comment kann flach kopiert werden, da es bereits eine unveränderbare class ist. Sie müssen eine neue Liste für TraceData , aber die Elemente selbst sind unveränderlich und erfordern keine spezielle Behandlung, um sie zu kopieren.

Wenn ich die Teilmenge wieder mit List.GetRange () erhalte, erhalte ich zuvor modifizierte Daten, nicht die Rohdaten, die an anderer Stelle abgerufen wurden.

Verwenden Sie Ihre neue DeepCopy Methode als solche:

 var pointsInRange = dataPoints .Select(x => x.DeepCopy()) .GetRange(start, length); 
 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DeepListCopy_testingSome { class Program { static void Main(string[] args) { List list1 = new List(); List list2 = new List(); //populate list1 for (int i = 0; i < 20; i++) { list1.Add(1); } /////// Console.WriteLine("\n int in each list1 element is:\n"); /////// foreach (int i in list1) { Console.WriteLine(" list1 elements: {0}", i); list2.Add(1); } /////// Console.WriteLine("\n int in each list2 element is:\n"); /////// foreach (int i in list2) { Console.WriteLine(" list2 elements: {0}", i); } ///////enter code here for (int i = 0; i < list2.Count; i++) { list2[i] = 2; } /////// Console.WriteLine("\n Printing list1 and list2 respectively to show\n" + " there is two independent lists,ie, two differens" + "\n memory locations after modifying list2\n\n"); foreach (int i in list1) { Console.WriteLine(" Printing list1 elements: {0}", i); } /////// Console.WriteLine("\n\n"); /////// foreach (int i in list2) { Console.WriteLine(" Printing list2 elements: {0}", i); } Console.ReadKey(); }//end of Static void Main }//end of class } 

Eine schnelle und generische Möglichkeit, ein Objekt tief zu serialisieren, ist die Verwendung von JSON.net . Die folgende Erweiterungsmethode ermöglicht das Serialisieren einer Liste beliebiger Objekte, ist jedoch in der Lage, Entity Framework-Navigationseigenschaften zu überspringen, da diese zu zirkulären Abhängigkeiten und unerwünschten Datenabrufen führen können.

Methode

 public static List DeepClone(this IList list, bool ignoreVirtualProps = false) { JsonSerializerSettings settings = new JsonSerializerSettings(); if (ignoreVirtualProps) { settings.ContractResolver = new IgnoreNavigationPropsResolver(); settings.PreserveReferencesHandling = PreserveReferencesHandling.None; settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; settings.Formatting = Formatting.Indented; } var serialized = JsonConvert.SerializeObject(list, settings); return JsonConvert.DeserializeObject>(serialized); } 

Verwendung

 var clonedList = list.DeepClone(); 

Standardmäßig serialisiert JSON.NET nur öffentliche Eigenschaften. Wenn private Eigenschaften ebenfalls geklont werden müssen, kann diese Lösung verwendet werden.

Diese Methode ermöglicht die schnelle (De-) Serialisierung komplexer Hierarchien von Objekten .