2010-11-19 13 views
14

Estoy tratando de hacer una copia profunda de una lista genérica, y me pregunto si hay alguna otra manera de crear el método de copiado y, de hecho, copiar sobre cada miembro de a uno por vez. Tengo una clase que se ve algo como esto:Copia en profundidad de la lista <T>

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

    private List<double> traceData; 
    public List<double> TraceData 
    { 
     get { return traceData; } 
     set { traceData = value; } 
    } 
} 

y tengo una lista de los datos anteriores, es decir List<Data>. Lo que estoy tratando de hacer es trazar un rastro de datos del subconjunto de la Lista en un gráfico, posiblemente con alguna escala o barrido en los datos. Obviamente no necesito trazar todo en la lista porque no encajan en la pantalla.

Inicialmente traté de obtener el subconjunto de la lista utilizando el método List.GetRange(), pero parece que debajo de List<double> se está copiando poco en lugar de copiar profundamente. Cuando vuelvo a obtener el subconjunto utilizando List.GetRange(), obtengo datos modificados previamente, no los datos brutos recuperados en otro lugar.

¿Alguien puede darme una dirección sobre cómo abordar esto? Muchas gracias.

+0

Skeet editado pero no sabía la respuesta? (8-O – Brad

+1

Tal vez me esté perdiendo algo, pero ¿qué sería una "Copia profunda" de una "Lista "? Es una lista de números, no es como una lista de clases de botones o algo que tenga miembros que puedan necesitar para ser copiado? – CodingGorilla

+0

Supongo que quiere decir que quiere hacer una copia profunda de cada objeto 'Datos', lo que implica que necesita copiar la lista en lugar de simplemente copiar la referencia. – mquander

Respuesta

10

La forma idiomática de abordar esto en C# es para implementar ICloneable en su Data, y escriba un método Clone que haga la copia profunda (y luego, presumiblemente, un método Enumerable.CloneRange que puede clonar parte de su lista a la vez). no es un truco incorporado o método de marco para hacerlo más fácil que eso.

A menos que la memoria y el rendimiento sean una preocupación real, sugiero que intente rediseñarlo para operar en objetos inmutables Data, en su lugar. Va a ser mucho más simple.

+2

+1: inmutable es realmente la única manera de ir :) Si el usuario no necesita acceso indexado a la 'Lista ', entonces una simple pila inmutable es más que suficiente. – Juliet

+2

[IClonable se considera una mala idea en estos días] (http://stackoverflow.com/questions/536349/why-no-icloneablet) – Liam

3

El más fácil (pero sucio) forma es aplicar ICloneable por su clase y usar a continuación el método de extensión:

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

Uso:

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

¿Por qué es esto "sucio"? – Brad

+4

ICloneable es como una erupción, comienza a rascarse y, antes de darse cuenta, está en todas partes. – jonnii

+2

Debe tenerse en cuenta que 'item.Clone()' no garantiza una copia profunda, y el método '.MemberwiseClone()' crea una copia superficial de los miembros internos. Ver msdn: http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx – Juliet

0

Si usted hace sus objetos inmutables que no es necesario que preocuparse de pasar alrededor de copias de ellos, entonces se podría hacer algo como:

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

otra cosa que puedes hacer es marcar su clase como serializable y usar serialización binaria Aquí es un ejemplo de trabajo

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<Test> 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<Test> secondList = binaryFormatter.Deserialize(stream) as List<Test>; 
      } 


      Console.ReadKey(); 
     } 
    } 
+0

La mayoría de las veces, recomiendo implementar una copia profunda a mano si puede ya que la serialización no es exactamente conocida por su velocidad de encendido. Sin embargo, he usado este estilo en el pasado con * enormes * árboles de datos variables, y funciona muy bien para el uso práctico. – Juliet

+0

@Juliet, gracias, solo estaba brindando una solución alternativa a las que ya se publicaron. –

0

Debido a que su colección es mutable, es necesario implementar la copia en profundidad mediante programación:

public class Data 
{ 
    public string Comment { get; set; } 
    public List<double> TraceData { get; set; } 

    public Data DeepCopy() 
    { 
     return new Data 
     { 
      Comment = this.Comment, 
      TraceData = this.TraceData != null 
       ? new List<double>(this.TraceData) 
       : null; 
     } 
    } 
} 

El campo Comment puede ser superficial copiado porque su ya una clase inmutable. Necesita crear una nueva lista para TraceData, pero los elementos en sí mismos son inmutables y no requieren un manejo especial para copiarlos.

Cuando consigo el subconjunto de nuevo utilizando List.GetRange(), consigo previamente datos modificados, no de los datos en bruto recuperados en otro lugar.

emplear el nuevo método DeepCopy como tal:

var pointsInRange = dataPoints 
    .Select(x => x.DeepCopy()) 
    .GetRange(start, length); 
6

puede probar esta

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

Gracias a DetoX83 article en el proyecto de código.

+2

Casi perfecto. Pero como era en mi caso, tienes matrices de objetos fuera de mscorlib o del conjunto actual, necesitarás usar 'elementType = Type.GetType (type.AssemblyQualifiedName.Replace (" [] ", string.Empty)); ' – makoshichi

5

Si la manera razonable es demasiado complicada para usted. Sugiero convertirme en algo y volver. Se puede hacer con BinaryFormatter o con un convertidor Json como Servicestack.Text, ya que es el más rápido en .Net.

Código debe ser algo como esto:

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

mcCloned no hará referencia a MC.

+1

ServiceStack usa GPL. Es posible que desee utilizar Json.Net – aloisdg

0
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<int> list1 = new List<int>(); 
      List<int> list2 = new List<int>(); 

      //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,i e, 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 
} 
+0

Intente utilizar una colección de objetos que no son primitivos. Entonces tu ejemplo no funcionará Editar: ¡Bueno, ahora veo que ni siquiera estás poblando la lista 2 con la lista 1! Esto no está relacionado con la pregunta ... –

0

Una rápida ygenérica manera de serializar profundamente un objeto es utilizar JSON.net. El siguiente método de extensión permite la serialización de una lista de objetos arbitrarios, pero puede omitir las propiedades de navegación de Entity Framework, ya que pueden dar lugar a dependencias circulares y recuperaciones de datos no deseados.

Método

public static List<T> DeepClone<T>(this IList<T> 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<List<T>>(serialized); 
} 

Uso

var clonedList = list.DeepClone(); 

Por defecto, JSON.NET serializa sólo propiedades públicas. Si también se deben clonar las propiedades privadas, se puede usar this solution.

Este método permite quick (de)serialization de complex hierarchies of objects.

Cuestiones relacionadas