it-swarm-es.tech

Objetos de clonación profunda

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?

2028
NakedBrunch

Si bien la práctica habitual es implementar la interfaz ICloneable (descrita aquí , así que no regurgitaré), aquí hay una copiadora de objetos de clonación de Nice que encontré en The Code Project hace un tiempo y lo incorporé en 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 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 en objectBeingCloned.Clone();.

EDIT(10 de enero de 2015) Pensé que volvería a esto, por mencionar que recientemente comencé a usar (Newtonsoft) Json para hacer esto, debería ser más ligero, y evita los gastos generales de etiquetas [serializables]. (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);
}
1592
johnc

Quería un clonador para objetos muy simples de listas y en su mayoría primitivos. Si su objeto está fuera de la caja JSON serializable, entonces este método hará el truco. 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 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);
    }
}
241
craastad

La razón para no usar ICloneable es no porque no tiene 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 opuesto a MemberwiseClone no 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ás 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 Copy que haga exactamente lo que quiero.

162
Ryan Lundy

Después de leer mucho sobre muchas de las opciones vinculadas aquí, y 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 la proporciona el enlace de Pedro77 en los comentarios de la pregunta.

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

Lo mejor que puedes hacer para clonar objetos en c sharp!

Ante todo, esas son todas nuestras opciones:

El article Fast Deep Copy por Expression Trees también tiene una comparación de rendimiento de la clonación por Serialización, Reflexión y Expression Trees.

¿Por qué elijo ICloneable (es decir, manualmente)

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

Todo su artículo gira alrededor de un ejemplo que trata de 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 visualizar 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 New seguido del nombre de la clase a menudo conduce a un código que no es extensible. El uso de clones, la aplicación del patrón prototipo, es una mejor manera de lograrlo. Sin embargo, usar el clon 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 da la capacidad de delegar la tarea de crear un objeto a una instancia de una clase en sí misma, lo que brinda extensibilidad y también la creación segura de objetos mediante el constructor de copias protegidas.

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 considera tener una clase derivada de la 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 [email protected]
This is person with [email protected]
SkilledPerson: This is person with [email protected]
SkilledPerson: This is person with [email protected]

Observe que, si mantenemos un recuento del número de objetos, el clon tal como se implementó aquí mantendrá un recuento correcto del número de objetos.

102
cregox

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

77
Nick

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

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);
    }
}
30
Michael White

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

¿Cómo usarlo?

En lugar de escribir sus propios métodos Clone o Copy con un tono de asignaciones entre los campos y las propiedades, haga que el programa lo haga por usted mismo, utilizando el Árbol de expresiones. El método GetClone<T>() marcado como método de extensión le permite llamarlo simplemente en su instancia:

var newInstance = source.GetClone();

Puede elegir lo que se debe copiar de source a newInstance usando CloningFlags enum:

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, porque la información de los miembros se debe recopilar solo una vez, antes de que GetClone<T> se use por primera vez para el tipo dado T.

También es más rápida 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 documentación .

Ejemplo de lista de depuración de expresiones 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 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 Clone para List<int>?

27
MarcinJuraszek

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

Al usar este método, no tiene que 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.

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

26
Michael Cox

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 una explicación más detallada sobre la clonación usando ICloneable, echa un vistazo a este artículo .

La larga respuesta 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 "error" en el .NET Framework. El método de serialización depende de que sus objetos sean serializables, que pueden no serlo y puede 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.

Vea el this artículo de la esquina del desarrollador para algunas opciones más (crédito para Ian).

20
Zach Burlingame

Lo mejor es implementar un método de extensiónlike

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

y luego usarlo en cualquier lugar en la solución por

var copy = anyObject.DeepClone();

Podemos tener las siguientes tres implementaciones:

  1. Por serialización (el código más corto)
  2. Por Reflexión - 5x más rápido
  3. Por árboles de expresiones - 20x más rápido

Todos los métodos vinculados funcionan bien y fueron probados profundamente.

19
frakon
  1. Básicamente, necesita implementar una interfaz ICloneable y luego realizar la copia de la estructura del objeto.
  2. Si se trata de una copia detallada de todos los miembros, debe asegurarse (no en relación con la solución que elija) de que todos los niños también pueden clonarse.
  3. A veces, debe estar al tanto de 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 cuidarse Acerca de la sesión adjunta de estos objetos.

Aclamaciones.

16
dimarzionist

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.

15
Michael Sander

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 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 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();
11
Stacked

Se me ocurrió esto para superar un .NET shortort que tiene que copiar manualmente 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();
        }
    }
}
10
Daniel Mošmondor

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 la velocidad máxima, 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ápida 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, tiene que implementar manualmente una ShallowCopy para cada nivel anidado en la clase, y una DeepCopy que llama 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, tiene que implementar manualmente una ShallowCopy para cada nivel anidado en la clase, y una DeepCopy que llama a todos los métodos de ShallowCopy mencionados. 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 "struct" y "class":

  • Si tiene un "struct", es un tipo de valor por lo que puede simplemente copiarlo, y el contenido se clonará (pero solo hará un clon superficial a menos que use el Técnicas en este post).
  • Si tiene un "clase", es un tipo de referencia, así 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 diferencias entre tipos de valores y tipos de referencias 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 a él. Esta suma de comprobación 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 anidadas a 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 una 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.

7
Contango

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

7
xr280xr

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 deja el código como está para tener el comportamiento predeterminado. Utiliza la reflexión, 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
7
kalisohn

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.

7
HappyDude

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

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

6
JerryGoyal

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.

5
LuckyLikey

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 CGbR Code Generator . 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 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 versión más reciente tiene más comprobaciones nulas, pero las dejé afuera para una mejor comprensión.

5
Toxantron

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();
}
5
Daniele D.

Creo que puedes intentar esto.

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

Sigue estos pasos:

  • Defina un ISelf<T> con una propiedad de solo lectura Self que devuelve T, y ICloneable<out T>, que deriva de ISelf<T> e incluye un método T Clone().
  • Luego defina un tipo CloneBase que implemente un protected virtual generic VirtualClone casting 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 VirtualClone principal aún no ha manejado.

Para una 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, de lo contrario, 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á que una rutina que espera que un derivado clonable de Foo funcione con un derivado clonable de DerivedFoo, pero también permita la creación de derivados no clonables de Foo.

4
supercat

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 recuerdo correctamente [DataContract] necesitaba un serializador diferente.

Requiere Sistema, 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;
    }

} 
3
Jeroen Ritmeijer

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.

3
LuckyLikey

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.

3
Chtiwi Malek

Ok, hay algunos ejemplos obvios 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 la memoria caché correctamente, clonará el objeto 1000000 en 4,6 s (medido por el Observador).

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 post en otra respuesta

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

3
Roma Borodov

Como casi todas las respuestas a esta pregunta han sido insatisfactorias o simplemente no funcionan en mi situación, he creado AnyClone que se implementa por completo con la reflexión y resolví todas las necesidades aquí. No pude conseguir que la serialización funcionara en un escenario complicado con una estructura compleja, y IClonable no es lo ideal, de hecho, ni siquiera debería ser necesario.

Los atributos de ignorar estándar son compatibles utilizando [IgnoreDataMember], [NonSerialized]. Admite colecciones complejas, propiedades sin configuradores, campos de solo lectura, etc.

Espero que ayude a alguien más que haya tenido los mismos problemas que yo.

2
Michael Brown

Cuando use protobuf-net de Marc Gravells como su serializador, la respuesta aceptada necesita algunas modificaciones leves, ya que el objeto a copiar no se atribuirá con [Serializable] y, por lo tanto, no es serializable y el método Clone lanzará una excepción.
Lo modifiqué para trabajar con protobuf-net:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

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

Esto verifica la presencia de un atributo [ProtoContract] y utiliza el propio formateador de protobufs para serializar el objeto.

1
Basti M

Extensión de C # que también admite tipos "no ISerializable ".

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

Uso

       var obj2 = obj1.DeepClone()
1
Sameera R.

Es increíble cuánto esfuerzo puede gastar con la interfaz IClonable, especialmente si tiene jerarquías de clase pesada. Además, MemberwiseClone funciona de alguna manera de forma extraña: no clona exactamente incluso el tipo de estructura normal tipo de lista.

Y, por supuesto, el dilema más interesante para la serialización es serializar las referencias anteriores, por ejemplo, Jerarquías de clase en las que tiene relaciones hijo-padre. Dudo que el serializador binario pueda ayudarte en este caso. (Terminará con bucles recursivos + desbordamiento de pila).

De alguna manera me gustó la solución propuesta aquí: ¿Cómo se hace una copia profunda de un objeto en .NET (C # específicamente)?

sin embargo, no era compatible con las listas, agregó que el soporte, también tuvo en cuenta la renovación de los padres. Para la regla de crianza solo que he hecho que el campo o la propiedad se llame "padre", DeepClone la ignorará. Es posible que desee decidir sus propias reglas para las referencias inversas: para las jerarquías de árbol puede ser "izquierda/derecha", etc.

Aquí hay un fragmento de código completo que incluye el código de prueba:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
1
TarmoPikaro

Sin embargo, otra respuesta JSON.NET. Esta versión funciona con clases que no implementan ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1
Matthew Watson

Un mapeador realiza una copia profunda. Para cada miembro de su objeto, crea un nuevo objeto y asigna todos sus valores. Funciona recursivamente en cada miembro interno no primitivo.

Te sugiero uno de los más rápidos, actualmente desarrollados activamente. Sugiero UltraMapper https://github.com/maurosampietro/UltraMapper

Paquetes de Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

Todos los enfoques genéricos son técnicamente válidos, pero solo quería agregar una nota de mí mismo, ya que rara vez necesitamos realmente una copia profunda, y me opondría enérgicamente a la copia profunda genérica en aplicaciones comerciales reales, ya que eso hace que tenga muchos. lugares donde los objetos se copian y luego se modifican explícitamente, es fácil perderse.

En la mayoría de las situaciones de la vida real, también desea tener tanto control granular sobre el proceso de copia como sea posible, ya que no solo está acoplado al marco de acceso a datos, sino que, en la práctica, los objetos de negocio copiados rara vez deberían ser 100% iguales. Piense en un ejemplo de ID de referencia utilizado por el ORM para identificar referencias de objetos, una copia en profundidad completa también copiará estos identificadores, por lo que mientras estén en la memoria los objetos serán diferentes, tan pronto como los envíe al almacén de datos, se quejará, por lo que Tiene que modificar estas propiedades manualmente después de copiar de todos modos y si el objeto cambia, debe ajustarlo en todos los lugares que utilizan la copia profunda genérica.

Ampliando la respuesta de @cregox con ICloneable, ¿qué es realmente una copia profunda? Es solo un objeto recién asignado en el montón que es idéntico al objeto original pero ocupa un espacio de memoria diferente, como tal, en lugar de usar una funcionalidad de clonador genérico, ¿por qué no crear un nuevo objeto?

Personalmente uso la idea de métodos de fábrica estáticos en mis objetos de dominio.

Ejemplo:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

Si alguien está viendo cómo puede estructurar la creación de instancias de objetos mientras mantiene el control total sobre el proceso de copia, esa es una solución con la que personalmente he tenido mucho éxito. Los constructores protegidos también lo hacen así, otros desarrolladores se ven obligados a usar los métodos de fábrica, lo que proporciona un punto único y ordenado de creación de instancias del objeto que encapsula la lógica de construcción dentro del objeto. También puede sobrecargar el método y tener varias lógicas de clonación para diferentes lugares si es necesario.

0

Encontré una nueva forma de hacerlo que es Emit.

Podemos usar Emit para agregar el IL a la aplicación y ejecutarlo. Pero no creo que sea una buena manera porque quiero perfeccionar esto, escribo mi respuesta.

El Emit puede ver el documento oficial y Guía

Debes aprender algo de IL para leer el código. Escribiré el código que puede copiar la propiedad en clase.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

El código puede ser copia en profundidad pero puede copiar la propiedad. Si quieres hacerlo en una copia profunda, puedes cambiarlo porque el IL es demasiado difícil y no puedo hacerlo.

0
lindexi

¿qué tal si simplemente se replantea dentro de un método que debería invocar básicamente un constructor de copia automática?

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

parece funcionar para mi

0
will_m

Esto copiará todas las propiedades legibles y grabables de un objeto a otro.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

y así es como lo usas:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

o para copiar todo:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
0
Ylli Prifti