example - icloneable c# ejemplo
La forma correcta de implementar ICloneable (6)
Aquí hay un copiar y pegar de un código de muestra que tenía por ahí y que escribí hace años.
Actualmente, evito tener diseños que requieran soporte Clon; Encontré que la mayoría de esos diseños son algo escamosos. En cambio, hago un uso extensivo de clases inmutables para evitar la necesidad de la clonación en primer lugar.
Habiendo dicho eso, aquí está el patrón de clonación de muestra:
using System;
using System.IO;
using System.Diagnostics;
/*
This code demonstrates a cloning pattern that you can use for class hierarchies.
The abstract base class specifies an abstract Clone() method which must be implemented by all derived classes.
Every class except the abstract base class must have a protected copy constructor.
This protected copy constructor will:
(1) call the base class'' copy constructor, and
(2) set any new fields introduced in the derived class.
This code also demonstrates an implementation of Equals() and CopyFrom().
*/
namespace CloningPattern
{
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
static class Program
{
static void Main()
{
Derived2 test = new Derived2()
{
IntValue = 1,
StringValue = "s",
DoubleValue = 2,
ShortValue = 3
};
Derived2 copy = Clone(test);
Console.WriteLine(copy);
}
static Derived2 Clone(AbstractBase item)
{
AbstractBase abstractBase = (AbstractBase) item.Clone();
Derived2 result = abstractBase as Derived2;
Debug.Assert(result != null);
return result;
}
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
public abstract class AbstractBase: ICloneable
{
// Sample data field.
public int IntValue { get; set; }
// Canonical way of providing a Clone() operation
// (except that this is abstract rather than virtual, since this class
// is itself abstract).
public abstract object Clone();
// Default constructor.
protected AbstractBase(){}
// Copy constructor.
protected AbstractBase(AbstractBase other)
{
if (other == null)
{
throw new ArgumentNullException("other");
}
this.copyFrom(other);
}
// Copy from another instance over the top of an already existing instance.
public virtual void CopyFrom(AbstractBase other)
{
if (other == null)
{
throw new ArgumentNullException("other");
}
this.copyFrom(other);
}
// Equality check.
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (object.ReferenceEquals(this, obj))
{
return true;
}
if (this.GetType() != obj.GetType())
{
return false;
}
AbstractBase other = (AbstractBase)obj;
return (this.IntValue == other.IntValue);
}
// Get hash code.
public override int GetHashCode()
{
return this.IntValue.GetHashCode();
}
// ToString() for debug purposes.
public override string ToString()
{
return "IntValue = " + IntValue;
}
// Implement copying fields in a private non-virtual method, called from more than one place.
private void copyFrom(AbstractBase other) // ''other'' cannot be null, so no check for nullness is made.
{
this.IntValue = other.IntValue;
}
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
public abstract class AbstractDerived: AbstractBase
{
// Sample data field.
public short ShortValue{ get; set; }
// Default constructor.
protected AbstractDerived(){}
// Copy constructor.
protected AbstractDerived(AbstractDerived other): base(other)
{
this.copyFrom(other);
}
// Copy from another instance over the top of an already existing instance.
public override void CopyFrom(AbstractBase other)
{
base.CopyFrom(other);
this.copyFrom(other as AbstractDerived);
}
// Comparison.
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
{
return true;
}
if (!base.Equals(obj))
{
return false;
}
AbstractDerived other = (AbstractDerived)obj; // This must succeed because if the types are different, base.Equals() returns false.
return (this.IntValue == other.IntValue);
}
// Get hash code.
public override int GetHashCode()
{
// "Standard" way of combining hash codes from subfields.
int hash = 17;
hash = hash * 23 + base.GetHashCode();
hash = hash * 23 + this.ShortValue.GetHashCode();
return hash;
}
// ToString() for debug purposes.
public override string ToString()
{
return base.ToString() + ", ShortValue = " + ShortValue;
}
// This abstract class doesn''t need to implement Clone() because no instances of it
// can ever be created, on account of it being abstract and all that.
// If you COULD, it would look like this (but you can''t so this won''t compile):
// public override object Clone()
// {
// return new AbstractDerived(this);
// }
// Implement copying fields in a private non-virtual method, called from more than one place.
private void copyFrom(AbstractDerived other) // Other could be null, so check for nullness.
{
if (other != null)
{
this.ShortValue = other.ShortValue;
}
}
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
public class Derived1: AbstractDerived
{
// Must declare a default constructor.
public Derived1(){}
// Sample data field.
public string StringValue{ get; set; }
// Implement Clone() by simply using this class'' copy constructor.
public override object Clone()
{
return new Derived1(this);
}
// Copy from another instance over the top of an already existing instance.
public override void CopyFrom(AbstractBase other)
{
base.CopyFrom(other);
this.copyFrom(other as Derived1);
}
// Equality check.
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
{
return true;
}
if (!base.Equals(obj))
{
return false;
}
Derived1 other = (Derived1)obj; // This must succeed because if the types are different, base.Equals() returns false.
return (this.StringValue == other.StringValue);
}
// Get hash code.
public override int GetHashCode()
{
// "Standard" way of combining hash codes from subfields.
int hash = 17;
hash = hash * 23 + base.GetHashCode();
hash = hash * 23 + this.StringValue.GetHashCode();
return hash;
}
// ToString() for debug purposes.
public override string ToString()
{
return base.ToString() + ", StringValue = " + StringValue;
}
// Protected copy constructor. Used to implement Clone().
// Also called by a derived class'' copy constructor.
protected Derived1(Derived1 other): base(other)
{
this.copyFrom(other);
}
// Implement copying fields in a private non-virtual method, called from more than one place.
private void copyFrom(Derived1 other) // Other could be null, so check for nullness.
{
if (other != null)
{
this.StringValue = other.StringValue;
}
}
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
public class Derived2: Derived1
{
// Must declare a default constructor.
public Derived2(){}
// Sample data field.
public double DoubleValue{ get; set; }
// Implement Clone() by simply using this class'' copy constructor.
public override object Clone()
{
return new Derived2(this);
}
// Copy from another instance over the top of an already existing instance.
public override void CopyFrom(AbstractBase other)
{
base.CopyFrom(other);
this.copyFrom(other as Derived2);
}
// Equality check.
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
{
return true;
}
if (!base.Equals(obj))
{
return false;
}
Derived2 other = (Derived2)obj; // This must succeed because if the types are different, base.Equals() returns false.
return (this.DoubleValue == other.DoubleValue);
}
// Get hash code.
public override int GetHashCode()
{
// "Standard" way of combining hash codes from subfields.
int hash = 17;
hash = hash * 23 + base.GetHashCode();
hash = hash * 23 + this.DoubleValue.GetHashCode();
return hash;
}
// ToString() for debug purposes.
public override string ToString()
{
return base.ToString() + ", DoubleValue = " + DoubleValue;
}
// Protected copy constructor. Used to implement Clone().
// Also called by a derived class'' copy constructor.
protected Derived2(Derived2 other): base(other)
{
// Canonical implementation: use ":base(other)" to copy all
// the base fields (which recursively applies all the way to the ultimate base)
// and then explicitly copy any of this class'' fields here:
this.copyFrom(other);
}
// Implement copying fields in a private non-virtual method, called from more than one place.
private void copyFrom(Derived2 other) // Other could be null, so check for nullness.
{
if (other != null)
{
this.DoubleValue = other.DoubleValue;
}
}
}
}
¿Cuál es la forma correcta de implementar ICloneable
en una jerarquía de clases? Digamos que tengo una clase abstracta DrawingObject
. Otra clase abstracta RectangularObject
hereda de DrawingObject
. Luego hay múltiples clases concretas como Shape
, Text
, Circle
, etc. que todas heredan de RectangularObject
. Quiero implementar ICloneable
en DrawingObject
y luego llevarlo por la jerarquía, copiar las propiedades disponibles en cada nivel y llamar al Clone
los padres en el siguiente nivel.
Sin embargo, el problema es que dado que las dos primeras clases son abstractas, no puedo crear sus objetos en el método Clone()
. Por lo tanto, debo duplicar el procedimiento de copia de propiedad en cada clase concreta. ¿O hay un mejor camino?
Como mínimo, solo permite que las clases concretas manejen la clonación, y las clases abstractas tienen constructores de copias protected
. Ahora, además de esto, desea poder tomar una variable de DrawingObject
y DrawingObject
así:
class Program
{
static void Main(string[] args)
{
DrawingObject obj1=new Circle(Color.Black, 10);
DrawingObject obj2=obj1.Clone();
}
}
Podrías considerar esta trampa, pero usaría métodos de extensión y reflexión:
public abstract class DrawingObject
{
public abstract void Draw();
public Color Color { get; set; }
protected DrawingObject(DrawingObject other)
{
this.Color=other.Color;
}
protected DrawingObject(Color color) { this.Color=color; }
}
public abstract class RectangularObject : DrawingObject
{
public int Width { get; set; }
public int Height { get; set; }
protected RectangularObject(RectangularObject other)
: base(other)
{
Height=other.Height;
Width=other.Width;
}
protected RectangularObject(Color color, int width, int height)
: base(color)
{
this.Width=width;
this.Height=height;
}
}
public class Circle : RectangularObject, ICloneable
{
public int Diameter { get; set; }
public override void Draw()
{
}
public Circle(Circle other)
: base(other)
{
this.Diameter=other.Diameter;
}
public Circle(Color color, int diameter)
: base(color, diameter, diameter)
{
Diameter=diameter;
}
#region ICloneable Members
public Circle Clone() { return new Circle(this); }
object ICloneable.Clone()
{
return Clone();
}
#endregion
}
public class Square : RectangularObject, ICloneable
{
public int Side { get; set; }
public override void Draw()
{
}
public Square(Square other)
: base(other)
{
this.Side=other.Side;
}
public Square(Color color, int side)
: base(color, side, side)
{
this.Side=side;
}
#region ICloneable Members
public Square Clone() { return new Square(this); }
object ICloneable.Clone()
{
return Clone();
}
#endregion
}
public static class Factory
{
public static T Clone<T>(this T other) where T : DrawingObject
{
Type t = other.GetType();
ConstructorInfo ctor=t.GetConstructor(new Type[] { t });
if (ctor!=null)
{
ctor.Invoke(new object[] { other });
}
return default(T);
}
}
Editar 1
Si se le advierte acerca de la velocidad (haciendo un reflejo cada vez), puede a) Guardar en caché el constructor en un diccionario estático.
public static class Factory
{
public static T Clone<T>(this T other) where T : DrawingObject
{
return Dynamic<T>.CopyCtor(other);
}
}
public static class Dynamic<T> where T : DrawingObject
{
static Dictionary<Type, Func<T, T>> cache = new Dictionary<Type,Func<T,T>>();
public static T CopyCtor(T other)
{
Type t=other.GetType();
if (!cache.ContainsKey(t))
{
var ctor=t.GetConstructor(new Type[] { t });
cache.Add(t, (x) => ctor.Invoke(new object[] { x }) as T);
}
return cache[t](other);
}
}
Creo que tengo una mejora sobre la excelente respuesta de @ johnny5. Simplemente defina los constructores de copia en todas las clases y en la clase base use la reflexión en el método Clone para encontrar el constructor de copia y ejecutarlo. Creo que esto es un poco más limpio, ya que no necesitas una pila de reemplazos de clonar y no necesitas el uso de MemberwiseClone (), que es simplemente un instrumento demasiado directo en muchas situaciones.
public abstract class AbstractCloneable : ICloneable
{
public int BaseValue { get; set; }
protected AbstractCloneable()
{
BaseValue = 1;
}
protected AbstractCloneable(AbstractCloneable d)
{
BaseValue = d.BaseValue;
}
public object Clone()
{
var clone = ObjectSupport.CloneFromCopyConstructor(this);
if(clone == null)throw new ApplicationException("Hey Dude, you didn''t define a copy constructor");
return clone;
}
}
public class ConcreteCloneable : AbstractCloneable
{
public int DerivedValue { get; set; }
public ConcreteCloneable()
{
DerivedValue = 2;
}
public ConcreteCloneable(ConcreteCloneable d)
: base(d)
{
DerivedValue = d.DerivedValue;
}
}
public class ObjectSupport
{
public static object CloneFromCopyConstructor(System.Object d)
{
if (d != null)
{
Type t = d.GetType();
foreach (ConstructorInfo ci in t.GetConstructors())
{
ParameterInfo[] pi = ci.GetParameters();
if (pi.Length == 1 && pi[0].ParameterType == t)
{
return ci.Invoke(new object[] { d });
}
}
}
return null;
}
}
Finalmente déjame hablar a favor de ICloneable. Si usas esta interfaz, serás golpeado por el estilo de la policía porque las Directrices de diseño de .NET Framework dicen que no se debe implementar porque, para citar las pautas, "cuando se utiliza un objeto que implementa un tipo con ICloneable, nunca se sabe lo que van a obtener. Esto hace que la interfaz sea inútil ". La implicación es que usted no sabe si está recibiendo una copia profunda o superficial. Bueno, esto es simplemente sofistería. ¿Esto implica que los constructores de copias nunca se deben usar porque "nunca se sabe lo que se va a obtener"? Por supuesto no. Si no sabe lo que va a obtener, esto es simplemente un problema con el diseño de la clase, no de la interfaz.
Dé a su clase base un método CreateClone()
protegido y reemplazable que cree una nueva instancia (vacía) de la clase actual. Luego Clone()
método Clone()
de la clase base invoque ese método para crear instancias polimórficas de una nueva instancia, a la cual la clase base puede luego copiar sus valores de campo.
Las clases derivadas no abstractas pueden anular el método CreateClone()
para instanciar la clase apropiada, y todas las clases derivadas que introducen nuevos campos pueden anular Clone()
para copiar sus valores de campo adicionales en la nueva instancia después de invocar la versión heredada de Clone()
.
public CloneableBase : ICloneable
{
protected abstract CloneableBase CreateClone();
public virtual object Clone()
{
CloneableBase clone = CreateClone();
clone.MyFirstProperty = this.MyFirstProperty;
return clone;
}
public int MyFirstProperty { get; set; }
}
public class CloneableChild : CloneableBase
{
protected override CloneableBase CreateClone()
{
return new CloneableChild();
}
public override object Clone()
{
CloneableChild clone = (CloneableChild)base.Clone();
clone.MySecondProperty = this.MySecondProperty;
return clone;
}
public int MySecondProperty { get; set; }
}
Si desea omitir el primer paso de anulación (al menos en el caso predeterminado), también puede suponer una firma de constructor predeterminada (por ejemplo, sin parámetro) e intentar crear una instancia de clonación utilizando esa firma de constructor con reflexión. De esta manera, solo las clases cuyos constructores no coincidan con la firma predeterminada deberán anular CreateClone()
.
Una versión muy simple de esa CreateClone()
predeterminada de CreateClone()
podría verse así:
protected virtual CloneableBase CreateClone()
{
return (CloneableBase)Activator.CreateInstance(GetType());
}
En mi opinión, la forma más clara es aplicar la serialización binaria con BinaryFormatter
en MemoryStream
.
Hay un hilo de MSDN sobre la clonación profunda en C # donde se sugiere el enfoque anterior.
Puede crear fácilmente un clon superficial con el método MemberwiseClone object
MemberwiseClone .
Ejemplo:
public abstract class AbstractCloneable : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}
Si no necesita nada como una copia profunda, no tendrá que hacer nada en las clases para niños.
El método MemberwiseClone crea una copia superficial creando un nuevo objeto y luego copiando los campos no estáticos del objeto actual al nuevo objeto. Si un campo es un tipo de valor, se realiza una copia bit por bit del campo. Si un campo es un tipo de referencia, la referencia se copia pero el objeto referido no; por lo tanto, el objeto original y su clon se refieren al mismo objeto.
Si necesita más inteligencia en la lógica de clonación, puede agregar un método virtual para manejar referencias:
public abstract class AbstractCloneable : ICloneable
{
public object Clone()
{
var clone = (AbstractCloneable) this.MemberwiseClone();
HandleCloned(clone);
return clone;
}
protected virtual HandleCloned(AbstractCloneable clone)
{
//Nothing particular in the base class, but maybe usefull for childs.
//Not abstract so childs may not implement this if they don''t need to.
}
}
public class ConcreteCloneable : AbstractCloneable
{
protected override HandleCloned(AbstractCloneable clone)
{
//Get wathever magic a base class could have implemented.
base.HandleCloned(clone);
//Clone is of the current type.
ConcreteCloneable obj = (ConcreteCloneable) clone;
//Here you have a superficial copy of "this". You can do wathever
//specific task you need to do.
//e.g.:
obj.SomeReferencedPropertie = this.SomeReferencedPropertie.Clone();
}
}