profunda - duplicar un objeto c#




Objetos de clonación profunda (20)

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ó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 versión más reciente tiene una revisión más nula, pero las dejé fuera para una mejor comprensión.

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.

No necesito a menudo esta funcionalidad, de modo que cuando ha sido necesario, he recurrido a crear un nuevo objeto y luego copiar cada propiedad individualmente, pero siempre me da 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 se pueda modificar sin que se reflejen cambios en el objeto original?


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

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

En otras palabras, vaya con otra respuesta a menos que tenga un cuello de botella de 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 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 .NET es capaz de.

Y el método ...

Para obtener la máxima velocidad, puede usar Nested MemberwiseClone para hacer una copia profunda . Es casi la misma velocidad que copiar una estructura de valor, y es mucho más rápido que (a) reflexión o (b) 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 llame a todos los métodos de 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

El uso de Nested MemberwiseClone en una clase casi tan rápido como copiar una estructura, y copiar una estructura es bastante parecido a la velocidad máxima teórica que es capaz de .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 entender cómo hacer una copia en profundidad 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, llama a la demo 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();
}

De nuevo, 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 llame a todos los métodos de 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, hay una gran diferencia entre una " estructura " y una " clase ":

  • Si tiene una " estructura ", es un tipo de valor, así que solo puede copiarlo, y los contenidos se clonarán (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 lo copia, todo lo que está haciendo es copiar el puntero en él. Para crear un clon verdadero, debe ser más creativo y usar las diferencias entre los tipos de valores y los 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 control para ayudar en la depuración

  • La clonación incorrecta de objetos puede llevar a errores muy difíciles de localizar. En el código de producción, tiendo a implementar una suma de comprobación para verificar que el objeto se haya clonado correctamente y que no haya sido corrompido por otra referencia. Esta suma de control se puede desactivar en el modo Release.
  • Encuentro que este método es muy útil: a menudo, solo desea clonar partes del objeto, no todo.

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

Un caso de uso excelente para este código es enviar 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 modifican una clase que poseen, y luego insertamos una copia completa de esta clase en un archivo ConcurrentQueue.
  • Luego tenemos uno (o más) hilos que extraen copias de estas clases y las manejan.

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 subprocesos disponibles en la máquina.

Actualizar

Al parecer, 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 perfilador.


Acabo de crear el proyecto de la biblioteca CloneExtensions . Realiza una clonación rápida y profunda mediante simples operaciones de asignación 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 de Clone o Copy con un tono de asignaciones entre campos y propiedades, haga que el programa lo haga por sí mismo, utilizando el Árbol de expresiones. GetClone<T>() marcado como método de extensión le permite llamarlo simplemente a su instancia:

var newInstance = source.GetClone();

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

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, incluyendo 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 los tipos que implementan ICollection.

¿Qué tan rápido es?

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

También es más rápido que la solución basada en la serialización cuando clona más y luego une instancias del mismo tipo T

y más...

Lea más sobre las expresiones generadas en la documentation .

Ejemplo de lista de depuración de expresiones para la 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 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 así como escribirías tu propio método de Clone para la List<int> ?


Bueno, estaba teniendo problemas al usar ICloneable en Silverlight, pero me gustó la idea de 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);
    }
}

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

Sí, MemberwiseClone hace una copia superficial, pero lo contrario de MemberwiseClone no es Clone ; Sería, quizás, 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 aclararán, porque obtendrá los comentarios de la interfaz en lugar de los del método de clonación del objeto).

Lo que normalmente hago es simplemente hacer un método de Copy que haga exactamente lo que quiero.


La respuesta corta es que usted hereda de la interfaz ICloneable y luego implementa la función .clone. Clone debe hacer una copia de memberwise 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, en realidad, algunos lo ven como un "error" en .NET Framework. El método de serialización depende de que sus objetos sean serializables, que pueden no serlo y es posible que no tenga control sobre ellos. 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 para todas las situaciones, como ICloneable se interpretó originalmente como tal.

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


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

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


Si bien la práctica habitual es implementar la interfaz ICloneable (que se describe here , por lo que no regurgitaré), aquí hay una 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.", "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 la clonación de todo cuando un objeto se vuelve demasiado complejo.

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

En caso de que prefiera usar los nuevos 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 en objectBeingCloned.Clone(); .

EDITAR (10 de enero de 2015) Pensé que revisaría esto, por mencionar que recientemente comencé a usar (Newtonsoft) Json para hacer esto, debería ser más ligero y evita la sobrecarga de 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);
}

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

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

Eso significa que si se refiere 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 o cualquier otra modificación de los objetos que se están clonando.


Aquí está 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;
}

Aquí una solución rápida y fácil que funcionó para mí sin tener que transmitir 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;

Asi es como lo uso

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}

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

        }

Úsalo 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égalos.


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

solo 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.


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

ser informado de que esta solución es bastante fácil, pero no es tan eficaz como otras soluciones pueden ser.

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


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 los requisitos de los clonadores. Se logra agregando anotaciones al código que se clonará o simplemente deja el código como es para tener el comportamiento predeterminado. Utiliza reflejo, escribe 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 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 será 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

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

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


Mantenga las cosas simples y use AutoMapper como otros lo mencionaron, es una pequeña biblioteca simple para asignar un objeto a otro ... Para copiar un objeto a otro con el mismo tipo, todo lo que necesita 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 usar en cualquier parte 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 usar 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 una deficiencia de .NET tener que copiar en forma manual 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 los bloques de métodos anónimos.

Mejor aún, use la lista genérica <T> cloner:

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




clone