2010-09-05 15 views
12

Quiero hacer una copia profunda de un objeto para poder cambiar la nueva copia y aún tener la opción de cancelar mis cambios y recuperar el objeto original.Crear una copia profunda en C#

Mi problema aquí es que el objeto puede ser de cualquier tipo, incluso de un ensamblaje desconocido. No puedo usar BinaryFormatter o XmlSerializer, porque el objeto innecesariamente tiene el atributo [Serializable].

he tratado de hacer esto utilizando el método Object.MemberwiseClone():

public object DeepCopy(object obj) 
{ 
    var memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); 

    var newCopy = memberwiseClone.Invoke(obj, new object[0]); 

    foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) 
    { 
     if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string)) 
     { 
      var fieldCopy = DeepCopy(field.GetValue(newCopy)); 
      field.SetValue(newCopy, fieldCopy); 
     } 
    } 
    return newCopy; 
} 

El problema de esto es que no está trabajando en un enumerable (matriz, lista, etc), no en un diccionario.

Entonces, ¿cómo puedo hacer una copia profunda de un objeto desconocido en C#?

TNX mucho!

+1

La invocación de 'MemberwiseClone' en un objeto arbitrario es _evil_. – SLaks

+0

He probado esto en matrices y diccionarios, y ambos parecen funcionar.Agregué un cheque al principio para null. Esto parece una solución de propósito general para objetos básicos. Como Slaks mencionó, no es una buena idea para filestreams y, por ejemplo, copiará el mismo identificador para la secuencia de archivos. Ambos dependerán de la secuencia de cierre o búsqueda. Si eres consciente de que no siempre es un clon profundo, es una solución interesante. –

+0

También he leído una solución en la que puede verificar la interfaz ICloneable. Si se implementa ICloneable, invoca de lo contrario solo crea una instancia vacía. –

Respuesta

13

Es completamente imposible copiar en profundidad un objeto arbitrario. Por ejemplo, ¿cómo manejarías un Control o un FileStream o un HttpWebResponse?

Su código no puede saber cómo funciona el objeto y qué se supone que contienen sus campos.

No haga esto.
Es una receta para el desastre.

+0

No veo lo que usted quiso decir aquí. ¿Por qué no puedo crear una copia profunda de un Control? Lo que trato de hacer es simplemente crear la misma instancia que tengo, pero en un nuevo 'lugar' en la memoria, así que si cambio una, la segunda no se verá afectada. Si pudiera, lo que haría sería tomar los bytes donde se encuentra el objeto en la memoria y copiarlos a una nueva ubicación en la memoria, luego crear un puntero para poder usarlo como cualquier otro objeto en .net. ¿Puedo hacerlo de alguna manera? –

+0

Acerca de FileStream o una HttpWebResponse, puedo estar tranquilo de que el objeto no es uno de esos. El objeto debe ser un objeto simple ('objeto de datos' o algo así). –

+1

@Orad: los controles implican interoperabilidad nativa (contienen un identificador de ventana). Si intentas copiar en profundidad uno, obtendrás dos controles que envuelven el mismo identificador de ventana. – SLaks

4

Hacer una copia profunda de un objeto arbitrario es bastante difícil. ¿Qué pasa si el objeto contiene acceso a un recurso como un archivo abierto con capacidades de escritura o una conexión de red? Sin saber qué tipo de información tiene el objeto, sería difícil hacer una copia de un objeto y hacer que funcione exactamente de la misma manera. Es posible que pueda usar el reflejo para hacer esto, pero sería bastante difícil. Para empezar, tendrías que tener algún tipo de lista para guardar todos los objetos que copiaste; de ​​lo contrario, si las referencias A y B hacen referencia a A, entonces podrías terminar en un ciclo sin fin.

4

De acuerdo con SLaks. Siempre necesita una semántica de copia personalizada para distinguir entre el clima que desea tener una copia profunda o una copia plana. (¿Qué es una referencia, qué referencia contenida en un sentido de referencia compuesta.)

El patrón del que está hablando es el memento pattern.

Lea el artículo sobre cómo implementarlo. Pero básicamente resulta crear una funcionalidad de copia personalizada. Ya sea interno en la clase o externo en una "copiadora".

+0

No veo cómo puedo usar ningún patrón. No sé cuál es el objeto que voy a copiar. ¿Puedes dar un ejemplo? –

+0

El patrón Memento está muy bien descrito aquí: http: //www.business-patterns.com/DesignPatterns/Behavioural/Memento.pdf. – schoetbi

+0

Todavía no entiendo cómo puedo usarlo en mi caso. En el patrón Memento, tendré que crear un recuerdo específico para cualquier clase que necesite para guardar su estado; pero en este caso necesito guardar el estado de cualquier objeto (incluso las clases de un ensamblaje que no está referenciado a mi ensamblaje). Además, necesito guardar solo un estado, el patrón de recuerdo (a mi entender) es principalmente para guardar muchos estados que el objeto pasó. –

2

Como han dicho otros, copiar profundamente un objeto arbitrario puede ser desastroso. Sin embargo, si está seguro de los objetos que intenta clonar, puede intentarlo.

dos cosas acerca de su método original:

  • En caso de referencias circulares que caerá en un bucle infinito;
  • El único caso especial que necesita preocuparse es copiar miembros que son matrices. Todo lo demás (Listas, Hashsets, Diccionarios, etc.) se reducirá a eso con el tiempo (o en el caso de los árboles y las listas vinculadas se podrán copiar de manera estándar).

Tenga en cuenta también que había un método que permitía crear un objeto de clase arbitrario sin invocar ninguno de sus constructores. BinaryFormatter lo usó y estaba a disposición del público. Lamentablemente, no recuerdo cómo se llamaba y dónde vivía. Algo sobre tiempos de ejecución o clasificación.

Actualización:System.Runtime.Serialization.FormatterServices.GetUninitializedObject Tenga en cuenta que

No se puede utilizar el método GetUninitializedObject para crear instancias de tipos que se derivan de la clase ContextBoundObject.

+2

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx – SLaks

+0

¡Hurra! ¡Ese es! –

+0

Muchas gracias, me has mostrado algunas cosas que no había visto antes. Intentaré actualizar mi método para que se encargue de los puntos que escribiste. –

1

Otra razón para no copiar objetos arbitrarios: es imposible saber, sin examinar el código detrás de un objeto, cómo ese objeto se relacionará con otros objetos en el sistema, o si ciertos valores son mágicos. Por ejemplo, dos objetos pueden contener referencias a matrices que contienen un solo entero que es igual a cinco. Ambas matrices se usan en otras partes del programa. Es posible que la matriz no tenga el valor cinco, sino el efecto que la escritura en la matriz pueda tener en la ejecución de otro programa. No hay forma de que un serializador cuyo autor no sabe para qué se utilizan las matrices podrá preservar esa relación.

3

Para responder a su pregunta, puede profunda copiar una serie llamando Array.CreateInstance y copiar el contenido de la matriz llamando GetValue y SetValue.

Sin embargo, no debe hacer esto para objetos arbitrarios.

Por ejemplo:

  • ¿Qué pasa con los controladores de eventos?
  • Algunos objetos tienen identificaciones únicas; terminará creando ID duplicados
  • ¿Qué pasa con los objetos que hacen referencia a sus padres?
2

Ok, he modificado un poco tu rutina. Necesitarás limpiarlo pero debería lograr lo que deseas. No probé esto en contra de los controles o los archivos distribuidos, y se debe tener cuidado con esas instancias.

He evitado Memberwise clonar para Activator.CreateInstance. Esto creará nuevas instancias de tipos de referencia y tipos de valores de copia. Si usa objetos con matrices multidimensionales, necesitará usar el rango de matriz e iterar para cada rango.

static object DeepCopyMine(object obj) 
    { 
     if (obj == null) return null; 

     object newCopy; 
     if (obj.GetType().IsArray) 
     { 
      var t = obj.GetType(); 
      var e = t.GetElementType(); 
      var r = t.GetArrayRank(); 
      Array a = (Array)obj; 
      newCopy = Array.CreateInstance(e, a.Length); 
      Array n = (Array)newCopy; 
      for (int i=0; i<a.Length; i++) 
      { 
       n.SetValue(DeepCopyMine(a.GetValue(i)), i); 
      } 
      return newCopy; 
     } 
     else 
     { 
      newCopy = Activator.CreateInstance(obj.GetType(), true); 
     } 

     foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) 
     { 
      if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string)) 
      { 
       var fieldCopy = DeepCopyMine(field.GetValue(obj)); 
       field.SetValue(newCopy, fieldCopy); 
      } 
      else 
      { 
       field.SetValue(newCopy, field.GetValue(obj)); 
      } 
     } 
     return newCopy; 
    } 
+0

Gracias, desarrollador errante, casi tuve esta versión literal y luego me encontré con esa última pieza ... grr! ... pero gracias! –

0

Aquí hay una elaboración del answer by @schoetbi. Necesitas decirle a la clase cómo clonarse. C# no distingue entre agregación y composición y utiliza referencias de objeto para ambos.

Si tenía una clase para almacenar información sobre un automóvil, podría tener campos de instancias, como motor, ruedas, etc. (composición), pero también fabricante (agregación). Ambos se almacenan como referencias.

Si clonó el automóvil, es de esperar que el motor y las ruedas también se clonen, pero tampoco querrá que el fabricante sea clonado.

Cuestiones relacionadas