Introducción:
La asignación de una variable en otra, de tipos complejos, como son la mayoría de los objetos instanciados de clases del .NET Framework o clases desarrollados por nosotros, dan por resultado dos variables referenciando al mismo objeto. Este concepto es conocido en otros lenguajes de programación como punteros.
Siendo así que al modificar el atributo de un objeto apuntado por dos variables, veremos reflejados el cambio en ambas variables, ya que las dos apuntan a la misma instancia de la clase. Es decir el objeto vive en memoria una vez sola y tiene dos variables que apuntan a él. Por circunstancias de las reglas de negocio es necesario a veces obtener una copia en memoria de un objeto, teniendo así dos variables apuntando a 2 objetos distintos que contienen los mismos valores.
Solución:
El Framework .NET provee a todos los objetos un método llamado MemberwiseClone() que crea una copia simple del objeto. Una copia simple es una copia de los miembros de la clase sin realizar una copia de los objetos a los que estos miembros apuntan. ¿Que pasa entonces cuando queremos copiar obejtos de estructuras complejas que tiene atributos que contienen objetos que contienen atributos que contienen objetos y así sucesivamente? Se puede implementar la interfaz IClonable que soporta el método Clone(), para soportar explícitamente una copia profunda de la clase. De todas formas esto requiere de un trabajo de creación de objetos y asignación manual de valores que debe hacerse por líneas de código. Esta tarea podía resultar muy ardua si la estructura del objeto a clonar es muy grande, y requiere una mantenimiento extra si la estructura de la clase se modifica.
Sería bueno también poder contar con algoritmo que nos permita ser reutilizado para todas las tareas de clonado de objetos. Es así que podemos recurrir a técnicas como Reflection que nos permita recursivamente inspeccionar y copiar los miembros del objeto y sus hijos. Es una técnica aceptable la cual requiere de un poco de tiempo de procesamiento para realizarse, la cual podría adoptarse en aplicaciones que no son de misión crítica.
Como todos los tipos que desarrollamos en.NET son serializables, podemos usar serialización de objetos para clonarlos. A continuación veremos un ejemplo donde utilizamos la técnica de seralización de un objeto en un espacio de memoria adicional o MemoryStream, y creamos una copia del objeto deserializandolo en una nueva instancia.
Desarrollo del problema:
Definición de la clase Cuadrado que contiene una colección de Puntos, la clonación de este objeto complejo no podrías hacerse con la técnica MemberwiseClone().
using System;
using System.Collections.Generic;
using System.Text;
namespace CopiaObjetos
{
[Serializable]
class Punto
{
public int Y;
public int X;
public Punto(int x, int y)
{
X = x;
Y = y;
}
}
[Serializable]
class Cuadrado : ICloneable
{
public string Color;
public string Textura;
public Punto Centro;
public List<Punto> Puntos;
public Cuadrado()
{
Puntos = new List<Punto>();
Centro = new Punto(0,0);
}
public object Clone()
{
return Copiadora.Copiar(this);
}
}
}
Clonación profunda del objeto Cuadrado usando la técnica de seralización y deserialización en memoria.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CopiaObjetos
{
class Program
{
static void Main(string[] args)
{
Cuadrado cuadrado = new Cuadrado();
cuadrado.Color = "Rojo";
cuadrado.Textura = "Rugosa";
cuadrado.Centro.Y = 10;
cuadrado.Centro.X = 10;
cuadrado.Puntos.Add(new Punto(5, 5));
cuadrado.Puntos.Add(new Punto(15, 5));
cuadrado.Puntos.Add(new Punto(15, 15));
cuadrado.Puntos.Add(new Punto(5,15));
// Copia de objeto por referencia
Cuadrado copiaCuadrada = cuadrado;
//Al cambiar el color en el objeto original, vemos el cambio en la copia
cuadrado.Color = "Verde";
Console.WriteLine("--------------- Color Original Modificado ------------");
Console.WriteLine("Color: {0}", cuadrado.Color);
Console.WriteLine("--------------- Color del Cuadrado Copiado por Referencia ------------");
Console.WriteLine("Color: {0}", copiaCuadrada.Color);
//Copia de objeto por clonación
copiaCuadrada = cuadrado.Clone() as Cuadrado;
//Inspeccionamos profundamente el objeto clonado
Console.WriteLine("--------------- Copia del cuadrado ------------");
Console.WriteLine("Color: {0}", copiaCuadrada.Color);
Console.WriteLine("Textura: {0}", copiaCuadrada.Textura);
Console.WriteLine("Centro: [{0},{1}]", copiaCuadrada.Centro.X, copiaCuadrada.Centro.Y);
Console.WriteLine("Abajo Izquierda: [{0},{1}]", copiaCuadrada.Puntos[0].X, copiaCuadrada.Puntos[0].Y);
Console.WriteLine("Abajo Derecha: [{0},{1}]", copiaCuadrada.Puntos[1].X, copiaCuadrada.Puntos[1].Y);
Console.WriteLine("Arriba Derecha: [{0},{1}]", copiaCuadrada.Puntos[2].X, copiaCuadrada.Puntos[2].Y);
Console.WriteLine("Arriba Izquierda: [{0},{1}]", copiaCuadrada.Puntos[3].X, copiaCuadrada.Puntos[3].Y);
//Modificamos el color del objeto original
Console.WriteLine("--------------- Color Original Modificado ------------");
cuadrado.Color = "Azul";
Console.WriteLine("Color: {0}", cuadrado.Color);
//Observamos que el valor del objeto cloando no se ha modificado
Console.WriteLine("--------------- Color del Cuadrado Copiado ------------");
Console.WriteLine("Color: {0}", copiaCuadrada.Color);
Console.ReadLine();
}
}
/// <summary>
/// Realiza una clonación de un objeto de estructura compleja
/// </summary>
public static class Copiadora
{
public static T Copiar<T>(T fuente)
{
//Verificamos que sea serializable antes de hacer la copia
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("El tipo de dato debe ser serializable.", "fuente");
}
if (Object.ReferenceEquals(fuente, null))
{
return default(T);
}
//Creamos un stream en memoria
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, fuente);
stream.Seek(0, SeekOrigin.Begin);
//Deserializamos la porción de memoria en el nuevo objeto
return (T)formatter.Deserialize(stream);
}
}
}
}
Descargar código fuente
Conclusión:
La ventaja de esta técnica es que nos provee un mecanismo rápido, sencillo y reutilizable para la clonación de objetos simples o estructuras de objetos complejas.
Este artículo fue publicado originalmente en la revista MTJ.NET de MSDN Latinoamérica en Mayo del 2006