otro - copia de objetos c#
Objetos de clonación profunda (30)
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?
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 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 una revisión más nula, pero las dejé fuera para una mejor comprensión.
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.
- Básicamente, necesita implementar una interfaz ICloneable y luego realizar la copia de la estructura del objeto.
- Si se trata de una copia detallada de todos los miembros, debe asegurarse (sin relación con la solución que elija) de que todos los niños también pueden clonarse.
- 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.
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);
}
}
Después de leer mucho sobre muchas de las opciones vinculadas aquí, y las posibles soluciones para este problema, creo que todas las opciones se resumen bastante bien en el enlace de Ian P. (todas las demás opciones son variaciones de esas) y la mejor solución la proporciona agiledeveloper.com/articles/cloning072002.htm en la pregunta comenta.
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:
- Manualmente con ICloneable , que es superficial y no es seguro para el tipo
- MemberwiseClone , que utiliza ICloneable
- Reflection utilizando Activator.CreateInstance y MemberwiseClone recursivo
- Serialization , como señala la respuesta preferida de johnc
- Lenguaje intermedio , que no tengo idea de cómo funciona
- Métodos de extensión , como este marco de clon personalizado por Havard Straden
- Arboles de expresion
El artículo Fast Deep Copy de Expression Trees también tiene una comparación de rendimiento de clonación por serialización, reflexión y expresión de árboles.
Por qué elijo ICloneable (es decir, manualmente)
agiledeveloper.com/articles/cloning072002.htm .
Todos sus artículos giran 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 utilizando el constructor de copia protegida.
Esperemos que esta implementación pueda aclarar las cosas:
public class Person : ICloneable
{
private final Brain brain; // brain is final since I do not want
// any transplant on it once created!
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
protected Person(Person another)
{
Brain refBrain = null;
try
{
refBrain = (Brain) another.brain.clone();
// You can set the brain in the constructor
}
catch(CloneNotSupportedException e) {}
brain = refBrain;
age = another.age;
}
public String toString()
{
return "This is person with " + brain;
// Not meant to sound rude as it reads!
}
public Object clone()
{
return new Person(this);
}
…
}
Ahora considera tener una clase derivada de Person.
public class SkilledPerson extends Person
{
private String theSkills;
public SkilledPerson(Brain aBrain, int theAge, String skills)
{
super(aBrain, theAge);
theSkills = skills;
}
protected SkilledPerson(SkilledPerson another)
{
super(another);
theSkills = another.theSkills;
}
public Object clone()
{
return new SkilledPerson(this);
}
public String toString()
{
return "SkilledPerson: " + super.toString();
}
}
Puede intentar ejecutar el siguiente código:
public class User
{
public static void play(Person p)
{
Person another = (Person) p.clone();
System.out.println(p);
System.out.println(another);
}
public static void main(String[] args)
{
Person sam = new Person(new Brain(), 1);
play(sam);
SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
play(bob);
}
}
La salida producida será:
This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e
Observe que, si mantenemos un recuento del número de objetos, el clon tal como se implementó aquí mantendrá un recuento correcto del número de objetos.
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).
Lo mejor es implementar un método de extensión como
public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
y luego usarlo en cualquier lugar en la solución por
var copy = anyObject.DeepClone();
Podemos tener las siguientes tres implementaciones:
- Por serialización (el código más corto)
- Por reflexión - 5 veces más rápido
- Por árboles de expresión - 20 veces más rápido
Todos los métodos vinculados funcionan bien y fueron probados profundamente.
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.
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, 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);
}
}
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.
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 .
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();
}
Creo que puedes intentar esto.
MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
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.
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 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 las 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://.com/a/34365709/4711853
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.
Sigue estos pasos:
- Defina un
ISelf<T>
con unaSelf
propiedad de solo lectura que devuelveT
, yICloneable<out T>
, que deriva deISelf<T>
e incluye un métodoT Clone()
. - Luego, defina un
CloneBase
tipo que implemente unaprotected virtual generic VirtualClone
conversiónMemberwiseClone
al tipo pasado. - Cada tipo derivado debe implementarse
VirtualClone
llamando al método de clonación base y luego hacer lo que sea necesario para clonar adecuadamente aquellos aspectos del tipo derivado que el método de VirtualClone principal aún no ha manejado.
Para obtener la máxima versatilidad de herencia, las clases que exponen la funcionalidad de clonación pública deben ser sealed
, pero derivan de una clase base que, por lo demás, es idéntica, excepto por la falta de clonación. En lugar de pasar variables del tipo clonable explícito, tome un parámetro de tipo ICloneable<theNonCloneableType>
. Esto permitirá que una rutina que espera que un derivado clonable Foo
funcione con un derivado clonable de DerivedFoo
, pero también permita la creación de derivados no clonables de Foo
.
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.
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 System, System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml ;
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of an object that is marked with ''[Serializable]'' or ''[DataContract]''
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (typeof(T).IsSerializable == true)
{
return CloneUsingSerializable<T>(source);
}
if (IsDataContract(typeof(T)) == true)
{
return CloneUsingDataContracts<T>(source);
}
throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
}
/// <summary>
/// Perform a deep Copy of an object that is marked with ''[Serializable]''
/// </summary>
/// <remarks>
/// Found on http://.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;
}
}
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();
}
}
}
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 sobre sí mismo. La reflexión es cara, pero funcionó bastante bien.