Pregunta

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 reflejen en el objeto original.

No suelo necesitar esta funcionalidad, así que cuando ha sido necesaria he recurrido a crear un nuevo objeto y luego copiar cada propiedad individualmente, pero siempre me deja con 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 ningún cambio se refleje en el objeto original?

¿Fue útil?

Solución

Si bien la práctica estándar es implementar la ICloneable interfaz (descrita aquí, así que no regurgitaré), aquí hay una bonita copiadora de objetos de clonación profunda que encontré en El proyecto del código hace un tiempo y lo incorporamos 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.", "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 serialice su objeto y luego lo deserialice en un objeto nuevo.El beneficio es que no tienes que preocuparte 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 a la que se hace referencia originalmente):

En caso de que prefieras usar el nuevo métodos de extensión de C# 3.0, cambie el método para que tenga 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, para mencionar que recientemente comencé a usar (Newtonsoft) Json para hacer esto, debiera ser más ligero y evita la sobrecarga de etiquetas [Serializables].(NÓTESE BIEN @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);
}

Otros consejos

Quería un clonador para objetos muy simples, en su mayoría primitivos y listas.Si su objeto es serializable JSON listo para usar, este método funcionará.Esto no requiere ninguna 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, puedes utilizar 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);
    }
}

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

Sí, MemberwiseClone hace una copia superficial, pero lo opuesto a MemberwiseClone no es Clone;sería, tal vez, DeepClone, 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 Clonar del objeto).

Lo que suelo hacer es simplemente hacer un Copy método que hace exactamente lo que quiero.

Después de leer mucho sobre muchas de las opciones vinculadas aquí y las posibles soluciones para este problema, creo Todas las opciones se resumen bastante bien en Ian P.enlace de (todas las demás opciones son variaciones de aquellas) y la mejor solución la proporciona Pedro77enlace de sobre los comentarios de la pregunta.

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

¡Lo mejor que puedes hacer para clonar objetos en do sostenido!

En primer lugar, esas son todas nuestras opciones:

El artículo Copia rápida y profunda mediante árboles de expresión También tiene una comparación de rendimiento de la clonación mediante árboles de serialización, reflexión y expresión.

¿Por qué elijo? IClonable (es decir.a mano)

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 a 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 imaginarse todos los problemas que cualquiera de los otros métodos anteriores puede generar o leer el artículo.

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

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

El resultado obtenido 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.

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

Método de extensión simple para copiar todas las propiedades públicas.Funciona para cualquier objeto y no es requieren que la clase sea [Serializable].Puede ampliarse 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 ) } );
        }
    };
}

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

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    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);
    }
}

acabo de crear CloneExtensions biblioteca proyecto.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 el tuyo Clone o Copy Los métodos con un tono de asignaciones entre campos y propiedades hacen que el programa lo haga usted mismo, utilizando 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();

Puedes elegir de qué se debe copiar source a newInstance usando CloningFlags enumeración:

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 (incluyendo acción, 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 campos públicos, no de solo lectura.
  • Valores de propiedades públicas con accesores 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 debe recopilarse sólo una vez, antes GetClone<T> se utiliza 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 un par de instancias del mismo tipo. T.

y más...

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

Listado de depuración de expresiones de ejemplo 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 que el siguiente código 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 muy parecido a cómo escribirías el tuyo propio? Clone método para List<int>?

Si ya estás usando una aplicación de terceros como ValorInyector o asignador automático, puedes 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 es necesario implementar ISerializable o ICloneable en sus objetos.Esto es común con el patrón MVC/MVVM, por lo que se han creado herramientas simples como esta.

ver la solución de clonación profunda valueinjecter en CodePlex.

La respuesta corta es que usted hereda de la interfaz ICloneable y luego implementa la función .clone.El clon debe hacer una copia por miembro y realizar una copia profunda en cualquier miembro que la 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 valores o implementen ICloneable y que sus miembros sean tipos de valores o implementen ICloneable, y así sucesivamente).

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

El largo La respuesta es "depende".Como han mencionado otros, ICloneable no es compatible con genéricos, requiere consideraciones especiales para las referencias de clases circulares y, de hecho, algunos lo consideran un "error" en el marco .NET.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 sobre él.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 única para todas las situaciones, como se interpretó originalmente que era ICloneable.

ver esto Artículo del rincón del desarrollador para ver algunas opciones más (crédito a Ian).

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 parte 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 fueron probados exhaustivamente.

  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) de que todos los elementos secundarios también sean clonables.
  3. A veces es necesario tener en cuenta alguna restricción 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 tener cuidado. sobre la conexión de sesiones de estos objetos.

Salud.

Si desea una verdadera clonación de tipos desconocidos, puede echar un vistazo aclon rápido.

Se trata de una 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 hace referencia varias veces al mismo objeto en su jerarquía, el clon también tendrá una única instancia a la que se hará referencia.

No hay necesidad de interfaces, atributos ni ninguna otra modificación en los objetos que se clonan.

Mantenga las cosas simples y use AutoMapeador como otros mencionaron, es una pequeña biblioteca simple para asignar un objeto a otro...Para copiar un objeto a otro del mismo tipo, todo lo que necesitas son 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 usarlo en todas partes de 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;
}

Al utilizar el método de extensión, las tres líneas se convierten en una sola línea:

MyType copy = source.Copy();

Se me ocurrió esto para superar un .NETO inconveniente de tener que realizar una copia profunda manual de List<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;
}

Intenté crear una línea que hiciera esto, pero no es posible debido a que el rendimiento no funciona dentro de bloques de métodos anónimos.

Mejor aún, utilice 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();
        }
    }
}

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

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

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

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

El siguiente método para realizar una clonación profunda 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 de la que es capaz .NET.

Y el método...

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

Tenga en cuenta que si tu usas MemberwiseClone anidado para una copia profunda, debe implementar manualmente un ShallowCopy para cada nivel anidado en la clase y un DeepCopy que llame a todos dichos 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 a continuación.

Aquí está el resultado 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 es casi tan rápido como copiar una estructura, y copiar una estructura está bastante cerca de la velocidad máxima teórica de la que .NET es capaz.

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 entender cómo hacer una copia profunda usando MemberwiseCopy, aquí está el proyecto de demostración que se usó 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 principal:

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

De nuevo, tenga en cuenta que si tu usas MemberwiseClone anidado para una copia profunda, debe implementar manualmente un ShallowCopy para cada nivel anidado en la clase y un DeepCopy que llame a todos dichos 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 vs.Tipos de referencias

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

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

Ver diferencias entre tipos de valores y tipos de referencias.

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

  • La clonación incorrecta de objetos 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 comprobación se puede desactivar en el modo Release.
  • Este método me parece bastante útil:A menudo, sólo desea clonar partes del objeto, no todo.

Realmente útil para desacoplar muchos subprocesos de muchos otros subprocesos.

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

  • Podemos tener uno (o más) subprocesos modificando una clase de su propiedad y luego insertando una copia completa de esta clase en un ConcurrentQueue.
  • Luego tenemos uno (o más) hilos que extraen copias de estas clases y se ocupan de ellas.

Esto funciona muy bien en la práctica y nos permite desacoplar muchos subprocesos (los productores) de uno o más subprocesos (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 serializar/deserializar clases anidadas y nos permite aprovechar todos los subprocesos 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.Quizás tenga que ver cómo se comparan con un perfilador.

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

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

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

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

Como no pude encontrar un clonador que cumpliera 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 dejando el código como está para que tenga el comportamiento predeterminado.Utiliza reflexión, cachés de tipos y se basa en reflejo más rápido.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 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 clonará profundamente la dirección, 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

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

Me gustan los Copyconstructores 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.

Generador de códigos

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

Todo lo que necesitas es una definición de clase parcial con ICloneable y 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 más comprobaciones nulas, pero las omití para una mejor comprensión.

Aquí hay una solución rápida y fácil que funcionó para mí sin depender de la 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();
}

Sigue estos pasos:

  • Definir un ISelf<T> con un solo lectura Self propiedad que regresa T, y ICloneable<out T>, que deriva de ISelf<T> e incluye un método T Clone().
  • Luego define un CloneBase tipo que implementa un protected virtual generic VirtualClone fundición MemberwiseClone al tipo pasado.
  • Cada tipo derivado debe implementar VirtualClone llamando 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 principal VirtualClone aún no ha manejado.

Para obtener la máxima versatilidad de herencia, las clases que exponen la funcionalidad de clonación pública deben ser sealed, pero derivan de una clase base que por lo demás 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 un derivado clonable de Foo trabajar con un derivado clonable de DerivedFoo, sino que también permiten la creación de derivados no clonables de Foo.

Creo que puedes probar esto.

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

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

Requiere Sistema, System.IO, System.Runtime.Serialización, 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;
    }

} 

Para clonar su objeto de clase puede usar el método Object.MemberwiseClone,

simplemente agrega esta función a tu clase:

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

luego, para realizar una copia independiente profunda, simplemente llame al método DeepCopy:

yourClass newLine = oldLine.DeepCopy();

espero que esto ayude.

Ok, hay un ejemplo obvio con 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á profundamente 1000000 objetos en 4,6 segundos (medido por Watcher).

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

de lo que toma propiedades almacenadas 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);
}

código completo verifique en mi publicación en otra respuesta

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

Si su árbol de objetos es serializable, también podría usar algo como esto

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

Tenga en cuenta que esta solución es bastante sencilla, pero no tiene tanto rendimiento como otras soluciones.

Y asegúrese de que si la Clase crece, solo se clonarán aquellos campos, que también se serializan.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top