Para clonar un objeto existente debo implementar en éste, la interfaz ICloneable, la cual posee un único método llamado Clone que debe retornar un tipo object.
El inconveniente principal es que el retorno no es del tipo espcífico (type-safe) sinó que es de tipo objeto. Esto obliga a los clientes de cualquier clase a que deban convertir el resultado al tipo específico, con el consecuente riesgo de errores o fallas en la aplicación.
Si poseo una clase cómo la de más abajo, llamada Usuario y decido clonarla, debo implementar el método Clone(). En el caso de mi clase Usuario, además posee un atributo del tipo de otra clase, llamada Recursos. Tambien es necesario que implemente Clone en las demás clases, sólo si estoy interesado en que se deben clonar esas tambien.
public class Usuario
{
private string _strNombre;
private Recursos _objRecursos;
void Usuario(string strNombre, Recursos objRecursos)
{
this._objRecursos = objRecursos;
this._strNombre = strNombre;
}
string nombre { get { return this._strNombre; } set { this._strNombre = value; }}
Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}
}
public class Recursos //Implementación omitida por claridad
{
public Recursos () {}
}
Al implementar ICloneable, nuestras clases quedan de la siguiente forma.
El método MemberWiseClone sólo clona internamente todos los tipos que son por valor, y no por referencia. En este caso, Recursos es una referencia en Usuario, por lo que es necesario hacer nosotros la llamada a su método clone.
public class Usuario : ICloneable
{
private string _strNombre;
private Recursos _objRecursos;
public Usuario(string strNombre, Recursos objRecursos)
{
this._objRecursos = objRecursos;
this._strNombre = strNombre;
}
string nombre { get { return this._strNombre; } set { this._strNombre = value; }}
Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}
public object Clone()
{
Usuario _objUsuarioTemp = (Usuario) this.MemberwiseClone();
_objUsuarioTemp.recursos = (Recursos) this.recursos.Clone();
return _objUsuarioTemp;
}
}
public class Recursos : ICloneable //Implementación omitida por claridad
{
public Recursos () { }
public object Clone()
{
return this.MemberwiseClone();
}
}
El inconveniente que tiene esto es que el resultado del método Clone es un objeto y no es posible cambiar el comportamiento (en esta versión del framework – 1.1 – por lo menos).
¿Qué se puede hacer?
Creamos un método llamado Clone(), pero que no implemente la interfaz sino que haga el llamado a MemberWiseClone(), lo trasforme al tipo de dato específico y luego lo retorne. Además, implementamos la interfaz ICloneable que ejecuta el método que acabamos de crear.
Implementando estos nuevos métodos, el resultado es el siguiente:
public class Usuario : ICloneable
{
private string _strNombre;
private Recursos _objRecursos;
public Usuario(string strNombre, Recursos objRecursos)
{
this._objRecursos = objRecursos;
this._strNombre = strNombre;
}
string nombre { get { return this._strNombre; } set { this._strNombre = value; }}
Recursos recursos { get { return this._objRecursos; } set { this._objRecursos = value; }}
object ICloneable.Clone()
{
return this.Clone();
}
public Usuario Clone()
{
Usuario _objUsuarioTemp = (Usuario) this.MemberwiseClone();
_objUsuarioTemp.recursos = this.recursos.Clone();//No es necesario transformar
return _objUsuarioTemp;
}
}
public class Recursos : ICloneable //Implementacion omitida por claridad
{
public Recursos ()
{
}
object ICloneable.Clone()
{
return this.Clone();
}
public Recursos Clone()
{
return (Recursos)this.MemberwiseClone();
}
}
El resultado de esto es que, si se trasforma (cast) el objeto a ICloneable, se ejecuta el método de la interfaz, pero si se usa con el tipo específico (objeto tipificado), se utiliza el método público. Véamelo en el ejemplo de más abajo.
Un código de ejemplo en una aplicación de consola, quedaría así.
class Class1
{
[STAThread]
static void Main(string[] args)
{
Usuario u1 = new Usuario(“www”, new Recursos());
Usuario u2;
u2 = u1.Clone(); // No es necesario el cast adicional.
//Si utilizamos el objeto a través de su interfaz, es necesario realizar el cast adicional.
u2 = (Usuario)((ICloneable)u1).Clone();
}
}
Hemos implementado un método de clonación que retorna tipos específicos en vez de objetos, simplificando el trabajo en los clientes que utilicen estas clases, ya que no son ellos los responsables de realizar la conversión.
Patrick Mac Kay.
Septiembre 2004.