Clonación de objetos de estructura compleja

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

Agregar a Technorati
Published Wednesday, October 24, 2007 7:49 PM by cwalzer
Filed under: ,

Comments

# re: Clonación de objetos de estructura compleja

Monday, December 10, 2007 4:24 PM by Didier

El metodo que clona superficialmente no es MemberwiseClone() ???

# re: Clonación de objetos de estructura compleja

Tuesday, December 11, 2007 7:55 AM by cwalzer

Tienes razón.  Ya lo corregí.  Gracias

# re: Clonación de objetos de estructura compleja

Tuesday, December 09, 2008 1:09 PM by Marcos Di Fazio

Muy util, me ayudó a resolver un problema de copiar y pegar que tenía con mi aplicación.

Leave a Comment

(required) 
(required) 
(optional)
(required) 
Powered by Community Server (Commercial Edition), by Telligent Systems