Copiar constructor versus Clonar ()

En C #, ¿cuál es la forma preferida de agregar funcionalidad de copia (profunda) a una clase? ¿Debería uno implementar el constructor de copia, o más bien derivar de ICloneable e implementar el método Clone() ?

Observación : escribí “profundo” entre paréntesis porque pensé que era irrelevante. Aparentemente, otros no están de acuerdo, así que pregunté si un constructor / operador / función de copia debe aclarar qué variante de copia implementa .

No deberías derivar de ICloneable .

La razón es que cuando Microsoft diseñó el framework .net, nunca especificaron si el método Clone() en ICloneable debería ser un clon profundo o superficial, por lo que la interfaz se rompe semánticamente ya que las personas que llaman no sabrán si la llamada será profunda o superficial. clonar el objeto.

En su lugar, debe definir sus propias IDeepCloneable (e IShallowCloneable ) con los DeepClone() (y ShallowClone() ).

Puede definir dos interfaces, una con un parámetro genérico para admitir la clonación fuertemente tipada y otra sin mantener la capacidad de clonación débilmente tipada para cuando está trabajando con colecciones de diferentes tipos de objetos clonables:

 public interface IDeepCloneable { object DeepClone(); } public interface IDeepCloneable : IDeepCloneable { T DeepClone(); } 

Que luego implementaría así:

 public class SampleClass : IDeepCloneable { public SampleClass DeepClone() { // Deep clone your object return ...; } object IDeepCloneable.DeepClone() { return this.DeepClone(); } } 

En general, prefiero usar las interfaces descritas en contraposición a un constructor de copia que mantiene la intención muy clara. Probablemente se suponga que un constructor de copia es un clon profundo, pero ciertamente no es una intención tan clara como usar una interfaz IDeepClonable.

Esto se discute en las Pautas de diseño de Framework .net y en el blog de Brad Abrams

(Supongo que si estás escribiendo una aplicación (a diferencia de un framework / biblioteca) para estar seguro de que nadie fuera de tu equipo llamará a tu código, no importa tanto y puedes asignarle un significado semántico “deepclone” a la interfaz .NET ICloneable, pero debes asegurarte de que esté bien documentado y bien entendido dentro de tu equipo. Personalmente, me apegaré a las pautas del framework.)

En C #, ¿cuál es la forma preferida de agregar funcionalidad de copia (profunda) a una clase? ¿Debería uno implementar el constructor de copia, o más bien derivar de ICloneable e implementar el método Clone ()?

El problema con ICloneable es, como han mencionado otros, que no especifica si se trata de una copia profunda o superficial, lo que la hace prácticamente inutilizable y, en la práctica, raramente utilizada. También devuelve un object , lo cual es un fastidio, ya que requiere mucho casting. (Y a pesar de que usted mencionó específicamente las clases en la pregunta, implementar ICloneable en una struct requiere boxeo).

Un constuctor de copia también sufre de uno de los problemas con ICloneable. No es obvio si un constructor de copia está haciendo una copia profunda o superficial.

 Account clonedAccount = new Account(currentAccount); // Deep or shallow? 

Lo mejor sería crear un método DeepClone (). De esta manera, la intención es perfectamente clara.

Esto plantea la cuestión de si debe ser un método estático o de instancia.

 Account clonedAccount = currentAccount.DeepClone(); // instance method 

o

 Account clonedAccount = Account.DeepClone(currentAccount); // static method 

A veces prefiero la versión estática, simplemente porque la clonación parece algo que se hace a un objeto en lugar de algo que el objeto está haciendo. En cualquier caso, habrá problemas para tratar cuando se clonan objetos que forman parte de una jerarquía de herencia, y la forma en que esos problemas son delt puede en última instancia impulsar el diseño.

 class CheckingAccount : Account { CheckAuthorizationScheme checkAuthorizationScheme; public override Account DeepClone() { CheckingAccount clone = new CheckingAccount(); DeepCloneFields(clone); return clone; } protected override void DeepCloneFields(Account clone) { base.DeepCloneFields(clone); ((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone(); } } 

Recomiendo usar un constructor de copia sobre un método de clonación principalmente porque un método de clonación le impedirá crear campos que readonly podrían haber sido si hubiera usado un constructor.

Si necesita clonación polimórfica, puede agregar un método Clone() abstract o virtual a su clase base que implemente con una llamada al constructor de copia.

Si necesita más de un tipo de copia (por ejemplo, profundo / superficial), puede especificarlo con un parámetro en el constructor de copia, aunque en mi experiencia me parece que generalmente lo que necesito es una mezcla de copia profunda y poco profunda.

Ex:

 public class BaseType { readonly int mBaseField; public BaseType(BaseType pSource) { mBaseField = pSource.mBaseField; } public virtual BaseType Clone() { return new BaseType(this); } } public class SubType : BaseType { readonly int mSubField; public SubType(SubType pSource) : base(pSource) { mSubField = pSource.mSubField; } public override BaseType Clone() { return new SubType(this); } } 

Existe un gran argumento de que debe implementar clone () usando un constructor de copia protegida

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 en una instancia de una clase en sí misma, proporcionando así extensibilidad y también, creando de forma segura los objetos utilizando el constructor de copia protegida.

Entonces esta no es una pregunta “contra”. Es posible que necesite tanto el (los) constructor (es) de copia (s) como una interfaz de clonación para hacerlo bien.

(Aunque la interfaz pública recomendada es la interfaz Clone () en lugar de la basada en Constructor).

No te enredes en el argumento explícito profundo o superficial en las otras respuestas. En el mundo real es casi siempre algo intermedio, y de cualquier manera, no debe ser la preocupación del que llama.

El contrato de Clone () es simplemente “no cambiará cuando cambie el primero”. La cantidad de gráfico que tiene que copiar, o cómo evitar la recursión infinita para que eso suceda, no debe afectar a la persona que llama.

No se recomienda implementar ICloneable debido al hecho de que no se especifica si se trata de una copia profunda o superficial, por lo que iría por el constructor o simplemente implementaría algo usted mismo. Tal vez llamarlo DeepCopy () para hacerlo realmente obvio.

Te encontrarás con problemas con los constructores de copia y las clases abstractas. Imagina que quieres hacer lo siguiente:

 abstract class A { public A() { } public A(A ToCopy) { X = ToCopy.X; } public int X; } class B : A { public B() { } public B(B ToCopy) : base(ToCopy) { Y = ToCopy.Y; } public int Y; } class C : A { public C() { } public C(C ToCopy) : base(ToCopy) { Z = ToCopy.Z; } public int Z; } class Program { static void Main(string[] args) { List list = new List(); B b = new B(); bX = 1; bY = 2; list.Add(b); C c = new C(); cX = 3; cZ = 4; list.Add(c); List cloneList = new List(); //Won't work //foreach (A a in list) // cloneList.Add(new A(a)); //Not this time batman! //Works, but is nasty for anything less contrived than this example. foreach (A a in list) { if(a is B) cloneList.Add(new B((B)a)); if (a is C) cloneList.Add(new C((C)a)); } } } 

Inmediatamente después de hacer lo anterior, comienzas a desear haber utilizado una interfaz o haber resuelto una implementación de DeepCopy () / ICloneable.Clone ().

El problema con ICloneable es bash y consistencia. Nunca está claro si es una copia profunda o superficial. Debido a eso, probablemente nunca se use de una manera u otra.

No creo que un constructor de copia pública sea más claro al respecto.

Dicho esto, introduciría un sistema de método que funciona para usted y transmitiría la intención (a’la autodefinición)

Si el objeto que intentas copiar es Serializable, puedes clonarlo serialándolo y deserializándolo. Entonces no necesita escribir un constructor de copia para cada clase.

No tengo acceso al código ahora pero es algo como esto

 public object DeepCopy(object source) { // Copy with Binary Serialization if the object supports it // If not try copying with XML Serialization // If not try copying with Data contract Serailizer, etc } 

Depende de la semántica de copia de la clase en cuestión, que debe definir usted mismo como el desarrollador. El método elegido generalmente se basa en los casos de uso previstos de la clase. Quizás tenga sentido implementar ambos métodos. Pero ambos comparten una desventaja similar: no está exactamente claro qué método de copia implementar. Esto debe estar claramente indicado en la documentación de su clase.

Para mí tener:

 // myobj is some transparent proxy object var state = new ObjectState(myobj.State); // do something myobject = GetInstance(); var newState = new ObjectState(myobject.State); if (!newState.Equals(state)) throw new Exception(); 

en lugar de:

 // myobj is some transparent proxy object var state = myobj.State.Clone(); // do something myobject = GetInstance(); var newState = myobject.State.Clone(); if (!newState.Equals(state)) throw new Exception(); 

parecía una statement de intenciones más clara.

Creo que debería haber un patrón estándar para objetos clonables, aunque no estoy seguro de cuál debería ser exactamente el patrón. Con respecto a la clonación, parece que hay tres tipos de clases:

  1. Aquellos que explícitamente apoyan la clonación profunda
  2. Aquellos en los que la clonación de miembros funcionará como clonación profunda, pero que no tienen ni necesitan apoyo explícito.
  3. Aquellos que no se pueden clonar de manera útil y profunda, y donde la clonación de miembros producirá malos resultados.

Hasta donde puedo decir, la única forma (al menos en .net 2.0) de obtener un nuevo objeto de la misma clase que un objeto existente es usar MemberwiseClone. Un buen patrón parecería tener una función “nueva” / “Sombras”. Clone que siempre devuelve el tipo actual, cuya definición siempre es llamar a MemberwiseClone y luego llamar a una subrutina virtual protegida CleanupClone (originalObject). La rutina CleanupCode debe llamar a base.Cleanupcode para manejar las necesidades de clonación del tipo de base y luego agregar su propia limpieza. Si la rutina de clonación tiene que usar el objeto original, tendría que estar encasillado, pero de lo contrario el único encasillado sería en la llamada a MemberwiseClone.

Desafortunadamente, el nivel más bajo de la clase que era del tipo (1) anterior en lugar del tipo (2) debería codificarse para suponer que sus tipos inferiores no necesitarían ningún soporte explícito para la clonación. Realmente no veo ninguna forma de evitar eso.

Aún así, creo que tener un patrón definido sería mejor que nada.

Por cierto, si uno sabe que el tipo de base admite iCloneable, pero no conoce el nombre de la función que utiliza, ¿hay alguna manera de hacer referencia a la función iCloneable.Clone del tipo base de uno?

Si lees todas las respuestas y discusiones interesantes, igual deberías preguntarte cómo exactamente copias las propiedades, todas explícitamente, o hay una forma más elegante de hacerlo. Si esa es su pregunta restante, eche un vistazo a esto (en StackOverflow):

¿Cómo puedo clonar “profundamente” las propiedades de clases de terceros utilizando un método de extensión genérico?

Describe cómo implementar un método de extensión CreateCopy() que crea una copia “profunda” del objeto que incluye todas las propiedades (sin tener que copiar la propiedad por propiedad manualmente).