Objetos de clonación profunda

2227

Quiero hacer algo como:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

Y luego realice cambios en el nuevo objeto que no se reflejan en el objeto original.

A menudo no necesito esta funcionalidad, así que cuando ha sido necesario, he recurrido a crear un nuevo objeto y luego copiar cada propiedad individualmente, pero siempre me deja la sensación de que hay una forma mejor o más elegante de manejar la situación.

¿Cómo puedo clonar o copiar en profundidad un objeto para que el objeto clonado pueda modificarse sin que se refleje ningún cambio en el objeto original?

DesnudoBrunch
fuente
81
Puede ser útil: "¿Por qué copiar un objeto es algo terrible?" agiledeveloper.com/articles/cloning072002.htm
Pedro77
stackoverflow.com/questions/8025890/… Otra solución ...
Felix K.
18
Deberías echar un vistazo a AutoMapper
Daniel Little
3
Su solución es mucho más compleja, me perdí leyéndola ... jejeje. Estoy usando una interfaz DeepClone. interfaz pública IDeepCloneable <T> {T DeepClone (); }
Pedro77
1
@ Pedro77 - Aunque, curiosamente, ese artículo termina diciendo que se cree un clonemétodo en la clase y luego se llame a un constructor interno y privado que se apruebe this. Así que copiar es turrible [sic], pero copiar con cuidado (y el artículo definitivamente vale la pena leer) no lo es. ; ^)
ruffin

Respuestas:

1716

Si bien la práctica estándar es implementar la ICloneableinterfaz (descrita aquí , por lo que no regurgitaré), aquí hay una buena copiadora de objetos de clonación profunda que encontré en The Code Project hace un tiempo y la incorporé a nuestras cosas.

Como se mencionó en otra parte, requiere que sus objetos sean serializables.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

La idea es que serializa su objeto y luego lo deserializa en un objeto nuevo. El beneficio es que no tiene que preocuparse por clonar todo cuando un objeto se vuelve demasiado complejo.

Y con el uso de métodos de extensión (también de la fuente referenciada originalmente):

En caso de que prefiera utilizar los nuevos métodos de extensión de C # 3.0, cambie el método para tener la siguiente firma:

public static T Clone<T>(this T source)
{
   //...
}

Ahora la llamada al método simplemente se convierte objectBeingCloned.Clone();.

EDITAR (10 de enero de 2015) Pensé en volver a visitar esto, por mencionar que recientemente comencé a usar (Newtonsoft) Json para hacer esto, debería ser más liviano y evitar la sobrecarga de las etiquetas [Serializable]. ( NB @atconway ha señalado en los comentarios que los miembros privados no se clonan utilizando el método JSON)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
johnc
fuente
24
stackoverflow.com/questions/78536/cloning-objects-in-c/… tiene un enlace al código anterior [y hace referencia a otras dos implementaciones similares, una de las cuales es más apropiada en mi contexto]
Ruben Bartelink
102
La serialización / deserialización implica una sobrecarga significativa que no es necesaria. Consulte la interfaz ICloneable y los métodos de clonación .MemberWise () en C #.
3Dave
18
@David, claro, pero si los objetos son livianos y el rendimiento alcanzado cuando se usa no es demasiado alto para sus requisitos, entonces es un consejo útil. No lo he usado intensamente con grandes cantidades de datos en un bucle, lo admito, pero nunca he visto una sola preocupación de rendimiento.
johnc
16
@Amir: en realidad, no: typeof(T).IsSerializabletambién es cierto si el tipo se ha marcado con el [Serializable]atributo. No tiene que implementar la ISerializableinterfaz.
Daniel Gehriger
11
Solo pensé en mencionar que si bien este método es útil, y lo he usado muchas veces, no es del todo compatible con Medium Trust, así que tenga cuidado si está escribiendo un código que necesita compatibilidad. BinaryFormatter accede a campos privados y, por lo tanto, no puede funcionar en el conjunto de permisos predeterminado para entornos de confianza parcial. Puede probar con otro serializador, pero asegúrese de que su interlocutor sepa que el clon puede no ser perfecto si el objeto entrante se basa en campos privados.
Alex Norcliffe
298

Quería un clonador para objetos muy simples, en su mayoría primitivos y listas. Si su objeto está fuera de la caja JSON serializable, entonces este método hará el truco. Esto no requiere modificación o implementación de interfaces en la clase clonada, solo un serializador JSON como JSON.NET.

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

Además, puede usar este método de extensión

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}
craastad
fuente
13
la solución es aún más rápida que la solución BinaryFormatter, comparación de rendimiento de serialización .NET
esskar
3
Gracias por esto. Pude hacer esencialmente lo mismo con el serializador BSON que viene con el controlador MongoDB para C #.
Mark Ewer
3
Esta es la mejor manera para mí, sin embargo, lo uso Newtonsoft.Json.JsonConvertpero es lo mismo
Pierre
1
Para que esto funcione, el objeto para clonar debe ser serializable como ya se mencionó, esto también significa, por ejemplo, que puede no tener dependencias circulares
Radomeit
2
Creo que esta es la mejor solución ya que la implementación se puede aplicar en la mayoría de los lenguajes de programación.
mr5
178

La razón para no usar ICloneable no es porque no tiene una interfaz genérica. La razón para no usarlo es porque es vago . No aclara si está obteniendo una copia superficial o profunda; eso depende del implementador.

Sí, MemberwiseClonehace una copia superficial, pero lo contrario de MemberwiseCloneno lo es Clone; sería, tal vez DeepClone, lo que no existe. Cuando utiliza un objeto a través de su interfaz ICloneable, no puede saber qué tipo de clonación realiza el objeto subyacente. (Y los comentarios XML no lo dejarán claro, porque obtendrá los comentarios de la interfaz en lugar de los del método Clone del objeto).

Lo que suelo hacer es simplemente crear un Copymétodo que haga exactamente lo que quiero.

Ryan Lundy
fuente
No estoy claro por qué ICloneable se considera vago. Dado un tipo como Dictionary (Of T, U), esperaría que ICloneable.Clone haga cualquier nivel de copia profunda y superficial que sea necesario para hacer que el nuevo diccionario sea un diccionario independiente que contenga las mismas T y U (contenido de estructura, y / o referencias de objetos) como el original. ¿Dónde está la ambigüedad? Sin duda, un ICloneable (Of T) genérico, que heredó ISelf (Of T), que incluía un método "Self", sería mucho mejor, pero no veo ambigüedad en la clonación profunda frente a la superficial.
supercat
31
Su ejemplo ilustra el problema. Supongamos que tiene un Diccionario <cadena, Cliente>. ¿Debe el Diccionario clonado tener los mismos objetos de Cliente que el original, o copias de esos objetos de Cliente? Hay casos de uso razonables para cualquiera de los dos. Pero ICloneable no deja claro cuál obtendrás. Por eso no es útil.
Ryan Lundy
@ Kyralessa El artículo de Microsoft MSDN en realidad establece este mismo problema de no saber si está solicitando una copia profunda o superficial.
enamorado
La respuesta del stackoverflow.com/questions/129389/… duplicado describe la extensión de copia, basada en MembershipClone recursivo
Michael Freidgeim
123

Después de leer mucho sobre muchas de las opciones vinculadas aquí, y las posibles soluciones para este problema, creo que todas las opciones se resumen bastante bien en el enlace de Ian P (todas las demás opciones son variaciones de esas) y la mejor solución es la que proporciona El enlace de Pedro77 sobre la pregunta comenta.

Así que solo copiaré partes relevantes de esas 2 referencias aquí. De esa manera podemos tener:

¡Lo mejor para clonar objetos en C sharp!

Ante todo, esas son todas nuestras opciones:

El artículo Fast Deep Copy de Expression Trees también tiene una comparación de rendimiento de la clonación por serialización, reflexión y árboles de expresión.

Por qué elijo ICloneable (es decir, manualmente)

El Sr. Venkat Subramaniam (enlace redundante aquí) explica con mucho detalle por qué .

Todo su artículo gira en torno a un ejemplo que intenta ser aplicable para la mayoría de los casos, utilizando 3 objetos: Persona , Cerebro y Ciudad . Queremos clonar a una persona, que tendrá su propio cerebro pero la misma ciudad. Puede imaginar todos los problemas que cualquiera de los otros métodos anteriores puede traer o leer el artículo.

Esta es mi versión ligeramente modificada de su conclusión:

Copiar un objeto especificando Newseguido del nombre de la clase a menudo conduce a un código que no es extensible. Usar el clon, la aplicación del patrón prototipo, es una mejor manera de lograrlo. Sin embargo, usar clon como se proporciona en C # (y Java) también puede ser bastante problemático. Es mejor proporcionar un constructor de copias protegido (no público) e invocarlo desde el método de clonación. Esto nos da la capacidad de delegar la tarea de crear un objeto a una instancia de una clase en sí misma, proporcionando así extensibilidad y también, creando de forma segura los objetos utilizando el constructor de copia protegida.

Esperemos que esta implementación pueda aclarar las cosas:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    
}

Ahora considere tener una clase derivada de Persona.

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

Puede intentar ejecutar el siguiente código:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

La salida producida será:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

Observe que, si mantenemos un recuento del número de objetos, el clon implementado aquí mantendrá un recuento correcto del número de objetos.

cregox
fuente
66
MS recomienda no usar ICloneablepara miembros públicos. "Debido a que las personas que llaman de Clone no pueden depender del método que realiza una operación de clonación predecible, recomendamos que ICloneable no se implemente en API públicas". msdn.microsoft.com/en-us/library/… Sin embargo, según la explicación dada por Venkat Subramaniam en su artículo vinculado, creo que tiene sentido usarlo en esta situación siempre que los creadores de los objetos ICloneable tengan un profundo comprensión de qué propiedades deberían ser copias profundas frente a copias superficiales (es decir, copia profunda Cerebro, copia superficial Ciudad)
BateTech
En primer lugar, estoy lejos de ser un experto en este tema (API públicas). Creo que por una vez ese comentario de la MS tiene mucho sentido. Y no creo que sea seguro asumir que los usuarios de esa API tendrán una comprensión tan profunda. Por lo tanto, solo tiene sentido implementarlo en una API pública si realmente no le importará a quien lo vaya a usar. Yo supongo que tienen algún tipo de UML haciendo muy explícita la distinción en cada propiedad podría ayudar. Pero me gustaría saber de alguien con más experiencia. : P
cregox
Puede usar el generador de clones CGbR y obtener un resultado similar sin escribir manualmente el código.
Toxantron
La implementación del lenguaje intermedio es útil
Michael Freidgeim el
No hay final en C #
Konrad
84

Prefiero un constructor de copia a un clon. La intención es más clara.

Mella
fuente
55
.Net no tiene constructores de copia.
Pop Catalin
48
Claro que sí: nuevo MyObject (objToCloneFrom) Simplemente declare un ctor que tome el objeto para clonar como parámetro.
Nick
30
No es lo mismo. Debe agregarlo a cada clase manualmente, y ni siquiera sabe si está garantizando una copia profunda.
Dave Van den Eynde
15
+1 para copiador. También debe escribir manualmente una función clone () para cada tipo de objeto, y buena suerte con eso cuando la jerarquía de su clase alcance algunos niveles de profundidad.
Andrew Grant
3
Sin embargo, con los constructores de copia se pierde la jerarquía. agiledeveloper.com/articles/cloning072002.htm
Será el
42

Método de extensión simple para copiar todas las propiedades públicas. Funciona para cualquier objeto y no requiere clase para ser [Serializable]. Se puede ampliar para otro nivel de acceso.

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}
Konstantin Salavatov
fuente
15
Esto, desafortunadamente, es defectuoso. Es equivalente a llamar a objectOne.MyProperty = objectTwo.MyProperty (es decir, solo copiará la referencia). No clonará los valores de las propiedades.
Alex Norcliffe
1
a Alex Norcliffe: el autor de la pregunta preguntó sobre "copiar cada propiedad" en lugar de clonar. en la mayoría de los casos, no se necesita la duplicación exacta de las propiedades.
Konstantin Salavatov
1
Pienso en usar este método pero con recurrencia. así que si el valor de una propiedad es una referencia, cree un nuevo objeto y llame a CopyTo nuevamente. Solo veo un problema, que todas las clases utilizadas deben tener un constructor sin parámetros. ¿Alguien ha intentado esto ya? También me pregunto si esto realmente funcionará con propiedades que contienen clases .net como DataRow y DataTable.
Koryu
33

Acabo de crear un proyecto de CloneExtensionsbiblioteca . Realiza una clonación rápida y profunda mediante operaciones de asignación simples generadas por la compilación del código de tiempo de ejecución de Expression Tree.

¿Cómo usarlo?

En lugar de escribir sus propios métodos Cloneo Copycon un tono de asignaciones entre campos y propiedades, haga que el programa lo haga por usted mismo, usando Expression Tree. GetClone<T>()El método marcado como método de extensión le permite simplemente llamarlo en su instancia:

var newInstance = source.GetClone();

Puede elegir de qué se debe copiar sourcepara newInstanceusar CloningFlagsenum:

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

¿Qué se puede clonar?

  • Primitivo (int, uint, byte, double, char, etc.), tipos inmutables conocidos (DateTime, TimeSpan, String) y delegados (incluidos Action, Func, etc.)
  • Anulable
  • T [] matrices
  • Clases y estructuras personalizadas, incluidas clases y estructuras genéricas.

Los siguientes miembros de clase / estructura se clonan internamente:

  • Valores de los campos públicos, no de solo lectura.
  • Valores de propiedades públicas con accesos get y set
  • Elementos de colección para tipos que implementan ICollection

¿Qué tan rápido es?

La solución es más rápida que la reflexión, porque la información de los miembros se debe recopilar solo una vez, antes de que GetClone<T>se use por primera vez para un tipo determinado T.

También es más rápido que la solución basada en serialización cuando clonas más de dos instancias del mismo tipo T.

y más...

Lea más sobre las expresiones generadas en la documentación .

Lista de depuración de expresiones de muestra para List<int>:

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

lo que tiene el mismo significado como el siguiente código de C #:

(source, flags, initializers) =>
{
    if(source == null)
        return null;

    if(initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

¿No es como escribirías tu propio Clonemétodo List<int>?

MarcinJuraszek
fuente
2
¿Cuáles son las posibilidades de que esto llegue a NuGet? Parece la mejor solución. ¿Cómo se compara con NClone ?
enamorado
Creo que esta respuesta debería ser votada más veces. La implementación manual de ICloneable es tediosa y propensa a errores, el uso de la reflexión o la serialización es lento si el rendimiento es importante y necesita copiar miles de objetos durante un corto período de tiempo.
Nightcoder
En absoluto, te equivocas con respecto a la reflexión, simplemente debes almacenar esto correctamente. Comprueba mi respuesta a continuación stackoverflow.com/a/34368738/4711853
Roma Borodov
31

Bueno, estaba teniendo problemas para usar ICloneable en Silverlight, pero me gustó la idea de la seralización, puedo seralizar XML, así que hice esto:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}
Michael White
fuente
31

Si ya está utilizando una aplicación de terceros como ValueInjecter o Automapper , puede hacer algo como esto:

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

Con este método no tiene que implementar ISerializableni ICloneableen sus objetos. Esto es común con el patrón MVC / MVVM, por lo que se han creado herramientas simples como esta.

vea la muestra de clonación profunda ValueInjecter en GitHub .

Michael Cox
fuente
26

Lo mejor es implementar un método de extensión como

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

y luego usarlo en cualquier lugar de la solución

var copy = anyObject.DeepClone();

Podemos tener las siguientes tres implementaciones:

  1. Por serialización (el código más corto)
  2. Por reflexión : 5 veces más rápido
  3. Por árboles de expresión : 20 veces más rápido

Todos los métodos vinculados funcionan bien y se probaron profundamente.

Frakon
fuente
El código de clonación usando árboles de expresión que ha publicado codeproject.com/Articles/1111658/… , está fallando con las versiones más nuevas de .Net framework con una excepción de seguridad, la operación podría desestabilizar el tiempo de ejecución , es básicamente una excepción debido al árbol de expresión mal formado, que se utiliza para generar el Func en tiempo de ejecución, compruebe si tiene alguna solución. De hecho, he visto problemas solo con objetos complejos con una jerarquía profunda, uno simple se copia fácilmente
Mrinal Kamboj
1
La implementación de ExpressionTree parece muy buena. Incluso funciona con referencias circulares y miembros privados. No se necesitan atributos. La mejor respuesta que he encontrado.
N73k
La mejor respuesta, funcionó muy bien, me salvaste el día
Adel Mourad
23

La respuesta corta es que usted hereda de la interfaz ICloneable y luego implementa la función .clone. Clone debe hacer una copia de miembro y realizar una copia profunda en cualquier miembro que lo requiera, luego devolver el objeto resultante. Esta es una operación recursiva (requiere que todos los miembros de la clase que desea clonar sean tipos de valor o implementen ICloneable y que sus miembros sean tipos de valor o implementen ICloneable, etc.).

Para obtener una explicación más detallada sobre la clonación con ICloneable, consulte este artículo .

La respuesta larga es "depende". Como lo mencionaron otros, ICloneable no es compatible con los genéricos, requiere consideraciones especiales para las referencias de clase circulares y, de hecho, algunos lo ven como un "error" en .NET Framework. El método de serialización depende de que sus objetos sean serializables, lo cual puede no serlo y es posible que usted no tenga control. Todavía hay mucho debate en la comunidad sobre cuál es la "mejor" práctica. En realidad, ninguna de las soluciones es la mejor práctica de talla única para todas las situaciones, como se interpretó originalmente como ICloneable.

Consulte el artículo de este Rincón del desarrollador para ver algunas opciones más (crédito para Ian).

Zach Burlingame
fuente
1
ICloneable no tiene una interfaz genérica, por lo que no se recomienda usar esa interfaz.
Karg
Su solución funciona hasta que necesita manejar referencias circulares, luego las cosas comienzan a complicarse, es mejor intentar implementar la clonación profunda usando la serialización profunda.
Pop Catalin
Desafortunadamente, tampoco todos los objetos son serializables, por lo que tampoco siempre puede usar ese método. El enlace de Ian es la respuesta más completa hasta ahora.
Zach Burlingame
19
  1. Básicamente, necesita implementar la interfaz ICloneable y luego realizar la copia de la estructura del objeto.
  2. Si se trata de una copia profunda de todos los miembros, debe asegurarse (sin relacionarse con la solución que elija) que todos los niños también son clonables.
  3. A veces, debe tener en cuenta algunas restricciones durante este proceso, por ejemplo, si copia los objetos ORM, la mayoría de los marcos permiten solo un objeto adjunto a la sesión y NO DEBE hacer clones de este objeto, o si es posible, debe preocuparse sobre la fijación de sesión de estos objetos.

Salud.

dimarzionista
fuente
44
ICloneable no tiene una interfaz genérica, por lo que no se recomienda usar esa interfaz.
Karg
Las respuestas simples y concisas son las mejores.
DavidGuaita
17

EDITAR: el proyecto se suspende

Si desea una verdadera clonación a tipos desconocidos, puede echar un vistazo a fastclone .

Esa es la clonación basada en expresiones que funciona aproximadamente 10 veces más rápido que la serialización binaria y mantiene la integridad completa del gráfico de objetos.

Eso significa: si se refiere varias veces al mismo objeto en su jerarquía, el clon también tendrá una única instancia referenciada.

No hay necesidad de interfaces, atributos o cualquier otra modificación a los objetos que se están clonando.

Michael Sander
fuente
Este parece ser bastante útil
LuckyLikey
Es más fácil comenzar a trabajar desde una instantánea de código que para el sistema general, especialmente uno cerrado. Es bastante comprensible que ninguna biblioteca pueda resolver todos los problemas con una sola toma. Deben hacerse algunas relajaciones.
TarmoPikaro
1
He probado tu solución y parece funcionar bien, ¡gracias! Creo que esta respuesta debería ser votada más veces. La implementación manual de ICloneable es tediosa y propensa a errores, el uso de la reflexión o la serialización es lento si el rendimiento es importante y necesita copiar miles de objetos durante un corto período de tiempo.
nightcoder
Lo intenté y no funcionó para mí. Lanza una excepción de MemberAccess.
Michael Brown
No funciona con versiones más nuevas de .NET y se suspende
Michael Sander
14

Mantenga las cosas simples y use AutoMapper como otros mencionaron, es una pequeña biblioteca simple para mapear un objeto a otro ... Para copiar un objeto a otro con el mismo tipo, todo lo que necesita es tres líneas de código:

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

El objeto de destino ahora es una copia del objeto de origen. ¿No es lo suficientemente simple? Cree un método de extensión para usar en todas partes en su solución:

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

El método de extensión se puede usar de la siguiente manera:

MyType copy = source.Copy();
Apilado
fuente
Tenga cuidado con este, funciona muy mal. Terminé cambiando a johnc answer, que es tan corto como este y funciona mucho mejor.
Agorilla
1
Esto solo hace una copia superficial.
N73k
11

Se me ocurrió esto para superar una deficiencia de .NET al tener que copiar manualmente en profundidad la Lista <T>.

Yo uso esto:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

Y en otro lugar:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

Traté de encontrar un oneliner que haga esto, pero no es posible, debido a que el rendimiento no funciona dentro de bloques de métodos anónimos.

Mejor aún, use el clonador genérico List <T>:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
Daniel Mošmondor
fuente
10

P. ¿Por qué elegiría esta respuesta?

  • Elija esta respuesta si desea la velocidad más rápida que .NET es capaz de hacer.
  • Ignore esta respuesta si desea un método de clonación realmente sencillo.

En otras palabras, elija otra respuesta, a menos que tenga un cuello de botella en el rendimiento que deba corregirse, y pueda probarlo con un generador de perfiles .

10 veces más rápido que otros métodos

El siguiente método para realizar un clon profundo es:

  • 10 veces más rápido que cualquier cosa que implique serialización / deserialización;
  • Bastante cerca de la velocidad máxima teórica que .NET es capaz de hacer.

Y el método ...

Para una velocidad máxima, puede usar Nested MemberwiseClone para hacer una copia profunda . Es casi la misma velocidad que copiar una estructura de valores, y es mucho más rápido que (a) la reflexión o (b) la serialización (como se describe en otras respuestas en esta página).

Tenga en cuenta que si usa Nested MemberwiseClone para una copia profunda , debe implementar manualmente una ShallowCopy para cada nivel anidado en la clase y una DeepCopy que llama a todos los métodos ShallowCopy para crear un clon completo. Esto es simple: solo unas pocas líneas en total, vea el código de demostración a continuación.

Aquí está la salida del código que muestra la diferencia de rendimiento relativa para 100,000 clones:

  • 1.08 segundos para Nested MemberwiseClone en estructuras anidadas
  • 4.77 segundos para Nested MemberwiseClone en clases anidadas
  • 39.93 segundos para serialización / deserialización

Usar Nested MemberwiseClone en una clase casi tan rápido como copiar una estructura, y copiar una estructura es bastante cercano a la velocidad máxima teórica que es capaz de hacer .NET.

Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:04.7795670,30000000

Demo 2 of shallow and deep copy, using structs and value copying:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details:
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:01.0875454,30000000

Demo 3 of deep copy, using class and serialize/deserialize:
  Elapsed time: 00:00:39.9339425,30000000

Para comprender cómo hacer una copia profunda con MemberwiseCopy, aquí está el proyecto de demostración que se utilizó para generar los tiempos anteriores:

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy<T>(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

Luego, llame a la demostración desde main:

void MyMain(string[] args)
{
    {
        Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
        var Bob = new Person(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {               
        Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
        var Bob = new PersonStruct(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details:\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {
        Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
        int total = 0;
        var sw = new Stopwatch();
        sw.Start();
        var Bob = new Person(30, "Lamborghini");
        for (int i = 0; i < 100000; i++)
        {
            var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
            total += BobsSon.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
    }
    Console.ReadKey();
}

Nuevamente, tenga en cuenta que si usa Nested MemberwiseClone para una copia profunda , debe implementar manualmente una ShallowCopy para cada nivel anidado en la clase, y una DeepCopy que llama a todos los métodos ShallowCopy para crear un clon completo. Esto es simple: solo unas pocas líneas en total, consulte el código de demostración anterior.

Tipos de valor frente a tipos de referencias

Tenga en cuenta que cuando se trata de clonar un objeto, hay una gran diferencia entre una " estructura " y una " clase ":

  • Si tiene una " estructura ", es un tipo de valor, por lo que puede copiarla y el contenido se clonará (pero solo hará un clon superficial a menos que use las técnicas en esta publicación).
  • Si tiene una " clase ", es un tipo de referencia , por lo que si la copia, todo lo que está haciendo es copiar el puntero a ella. Para crear un verdadero clon, debe ser más creativo y utilizar las diferencias entre los tipos de valores y los tipos de referencias, lo que crea otra copia del objeto original en la memoria.

Vea las diferencias entre los tipos de valores y los tipos de referencias .

Sumas de verificación para ayudar en la depuración

  • Clonar objetos incorrectamente puede provocar errores muy difíciles de localizar. En el código de producción, tiendo a implementar una suma de verificación para verificar que el objeto se haya clonado correctamente y que no haya sido dañado por otra referencia al mismo. Esta suma de verificación se puede desactivar en el modo Release.
  • Este método me parece bastante útil: a menudo, solo desea clonar partes del objeto, no todo.

Realmente útil para desacoplar muchos hilos de muchos otros hilos

Un excelente caso de uso para este código es alimentar clones de una clase o estructura anidada en una cola, para implementar el patrón productor / consumidor.

  • Podemos tener uno (o más) subprocesos que modifiquen una clase que poseen, y luego empujar una copia completa de esta clase a un ConcurrentQueue .
  • Luego tenemos uno (o más) hilos que extraen copias de estas clases y se ocupan de ellas.

Esto funciona extremadamente bien en la práctica y nos permite desacoplar muchos hilos (los productores) de uno o más hilos (los consumidores).

Y este método también es increíblemente rápido: si usamos estructuras anidadas, es 35 veces más rápido que la serialización / deserialización de clases anidadas, y nos permite aprovechar todos los hilos disponibles en la máquina.

Actualizar

Aparentemente, ExpressMapper es tan rápido, si no más rápido, que la codificación manual como la anterior. Puede que tenga que ver cómo se comparan con un generador de perfiles.

Aplazamiento de pago
fuente
Si copia una estructura, obtiene una copia superficial, aún podría necesitar una implementación específica para una copia profunda.
Lasse V. Karlsen
@Lasse V. Karlsen. Sí, tienes toda la razón, he actualizado la respuesta para aclarar esto. Este método se puede usar para hacer copias profundas de estructuras y clases. Puede ejecutar el código de demostración de ejemplo incluido para mostrar cómo se hace, tiene un ejemplo de clonación profunda de una estructura anidada y otro ejemplo de clonación profunda de una clase anidada.
Contango
9

En general, implementa la interfaz ICloneable e implementa Clone usted mismo. Los objetos C # tienen un método integrado MemberwiseClone que realiza una copia superficial que puede ayudarlo con todas las primitivas.

Para una copia profunda, no hay forma de saber cómo hacerlo automáticamente.

HappyDude
fuente
ICloneable no tiene una interfaz genérica, por lo que no se recomienda usar esa interfaz.
Karg
8

Lo he visto implementado a través de la reflexión también. Básicamente, había un método que iteraba a través de los miembros de un objeto y los copiaba adecuadamente al nuevo objeto. Cuando llegó a tipos de referencia o colecciones, creo que hizo una llamada recursiva sobre sí mismo. La reflexión es costosa, pero funcionó bastante bien.

xr280xr
fuente
8

Aquí hay una implementación de copia profunda:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}
dougajmcdonald
fuente
2
Esto se ve como clon miembro por miembro, porque no lo hace consciente de propiedades de tipo de referencia
sll
1
Si desea un rendimiento increíblemente rápido, no elija esta implementación: utiliza la reflexión, por lo que no será tan rápido. Por el contrario, "la optimización prematura es el mal", ignore el lado del rendimiento hasta que haya ejecutado un generador de perfiles.
Contango
1
¿CreateInstanceOfType no está definido?
MonsterMMORPG
Falla en el interger: "El método no estático requiere un objetivo".
Mr.B
8

Como no pude encontrar un clonador que cumpla con todos mis requisitos en diferentes proyectos, creé un clonador profundo que se puede configurar y adaptar a diferentes estructuras de código en lugar de adaptar mi código para cumplir con los requisitos de los clonadores. Se logra agregando anotaciones al código que se clonará o simplemente dejará el código como debe tener el comportamiento predeterminado. Utiliza la reflexión, el tipo de cachés y se basa en fasterflect . El proceso de clonación es muy rápido para una gran cantidad de datos y una alta jerarquía de objetos (en comparación con otros algoritmos basados ​​en la reflexión / serialización).

https://github.com/kalisohn/CloneBehave

También disponible como paquete nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Por ejemplo: el siguiente código hará DeepClone Address, pero solo realizará una copia superficial del campo _currentJob.

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
Kalisohn
fuente
7

Generador de códigos

Hemos visto muchas ideas desde la serialización sobre la implementación manual hasta la reflexión y quiero proponer un enfoque totalmente diferente usando el generador de código CGbR . El método de generación de clones es eficiente en memoria y CPU y, por lo tanto, 300 veces más rápido que el DataContractSerializer estándar.

Todo lo que necesita es una definición de clase parcial con ICloneabley el generador hace el resto:

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers.Add(value);
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

Nota: La última versión tiene controles más nulos, pero los dejé fuera para una mejor comprensión.

Toxantron
fuente
6

Me gusta Copyconstructors así:

    public AnyObject(AnyObject anyObject)
    {
        foreach (var property in typeof(AnyObject).GetProperties())
        {
            property.SetValue(this, property.GetValue(anyObject));
        }
        foreach (var field in typeof(AnyObject).GetFields())
        {
            field.SetValue(this, field.GetValue(anyObject));
        }
    }

Si tienes más cosas para copiar, agrégalas

LuckyLikey
fuente
6

Este método resolvió el problema para mí:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

Úselo así: MyObj a = DeepCopy(b);

JerryGoyal
fuente
6

Aquí una solución rápida y fácil que funcionó para mí sin contar con Serialización / Deserialización.

public class MyClass
{
    public virtual MyClass DeepClone()
    {
        var returnObj = (MyClass)MemberwiseClone();
        var type = returnObj.GetType();
        var fieldInfoArray = type.GetRuntimeFields().ToArray();

        foreach (var fieldInfo in fieldInfoArray)
        {
            object sourceFieldValue = fieldInfo.GetValue(this);
            if (!(sourceFieldValue is MyClass))
            {
                continue;
            }

            var sourceObj = (MyClass)sourceFieldValue;
            var clonedObj = sourceObj.DeepClone();
            fieldInfo.SetValue(returnObj, clonedObj);
        }
        return returnObj;
    }
}

EDITAR : requiere

    using System.Linq;
    using System.Reflection;

Así es como lo usé

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
Daniele D.
fuente
5

Sigue estos pasos:

  • Defina un ISelf<T>con una Selfpropiedad de solo lectura que devuelve T, y ICloneable<out T>, que se deriva de ISelf<T>e incluye un método T Clone().
  • Luego, defina un CloneBasetipo que implemente una protected virtual generic VirtualCloneconversión MemberwiseCloneal tipo pasado.
  • Cada tipo derivado debe implementarse VirtualClonellamando al método de clonación base y luego haciendo lo que sea necesario para clonar adecuadamente aquellos aspectos del tipo derivado que el método VirtualClone padre aún no ha manejado.

Para una máxima versatilidad de herencia, las clases que exponen la funcionalidad de clonación pública deberían ser sealed, pero derivar de una clase base que de otro modo es idéntica, excepto por la falta de clonación. En lugar de pasar variables del tipo clonable explícito, tome un parámetro de tipo ICloneable<theNonCloneableType>. Esto permitirá una rutina que espera que una derivada clonable de Foofuncione con una derivada clonable de DerivedFoo, pero también permitirá la creación de derivados no clonables de Foo.

Super gato
fuente
5

Creo que puedes probar esto.

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
Sudhanva Kotabagi
fuente
4

He creado una versión de la respuesta aceptada que funciona con '[Serializable]' y '[DataContract]'. Ha pasado un tiempo desde que lo escribí, pero si no recuerdo mal, [DataContract] necesitaba un serializador diferente.

Requiere System, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 
Jeroen Ritmeijer
fuente
4

Ok, hay algunos ejemplos obvios con la reflexión en esta publicación, PERO la reflexión suele ser lenta, hasta que comienzas a almacenarla en caché correctamente.

si lo almacena en caché correctamente, clonará en profundidad el objeto 1000000 en 4,6s (medido por Watcher).

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

de lo que toma propiedades en caché o agrega nuevas al diccionario y las usa simplemente

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

verificación de código completo en mi publicación en otra respuesta

https://stackoverflow.com/a/34365709/4711853

Roma Borodov
fuente
2
Las llamadas prop.GetValue(...)siguen siendo reflejo y no se pueden almacenar en caché. Sin embargo, en un árbol de expresión se compila, más rápido
Tseng
4

Como casi todas las respuestas a esta pregunta han sido insatisfactorias o simplemente no funcionan en mi situación, escribí AnyClone, que se implementó completamente con reflexión y resolvió todas las necesidades aquí. No pude lograr que la serialización funcione en un escenario complicado con una estructura compleja, y no IClonablees lo ideal, de hecho, ni siquiera debería ser necesario.

Estándar ignorar los atributos son compatibles usando [IgnoreDataMember], [NonSerialized]. Admite colecciones complejas, propiedades sin setters, campos de solo lectura, etc.

Espero que ayude a alguien más que se encontró con los mismos problemas que yo.

Michael Brown
fuente
4

Descargo de responsabilidad: soy el autor del paquete mencionado.

Me sorprendió cómo las principales respuestas a esta pregunta en 2019 todavía usan la serialización o la reflexión.

La serialización es limitante (requiere atributos, constructores específicos, etc.) y es muy lenta

BinaryFormatterrequiere el Serializableatributo, JsonConverterrequiere un constructor o atributos sin parámetros, ninguno maneja muy bien los campos o interfaces de solo lectura y ambos son 10-30x más lentos de lo necesario.

Árboles de expresión

En su lugar, puede usar Expression Trees o Reflection.Emit para generar código de clonación solo una vez, luego usar ese código compilado en lugar de reflexión lenta o serialización.

Habiendo encontrado el problema yo mismo y no encontré una solución satisfactoria, decidí crear un paquete que haga exactamente eso y funcione con cada tipo y sea casi tan rápido como el código escrito personalizado .

Puede encontrar el proyecto en GitHub: https://github.com/marcelltoth/ObjectCloner

Uso

Puede instalarlo desde NuGet. Obtenga el ObjectClonerpaquete y úselo como:

var clone = ObjectCloner.DeepClone(original);

o si no le importa contaminar su tipo de objeto con extensiones, obtenga ObjectCloner.Extensionsy escriba:

var clone = original.DeepClone();

Actuación

Un punto de referencia simple de clonar una jerarquía de clases mostró un rendimiento ~ 3 veces más rápido que usando Reflection, ~ 12 veces más rápido que la serialización de Newtonsoft.Json y ~ 36 veces más rápido que el altamente sugerido BinaryFormatter.

Marcell Toth
fuente