sábado, abril 07, 2007

Clone, serialización y eventos

La semana pasada, trabajando en un proyecto, tuve la necesidad de poder obtener una copia de un objeto, al decir copia no me refiero a asignar mi objeto a otra variable, ya que esto no saca una copia, simplemente crea otra referencia a mi objeto, en cambio la copia crea otra nueva instancia del objeto en memoria, con los datos del objeto original, pero que cualquier cambio realizado a cualquiera de ellos no afecta al otro; esto en .NET se logra implementando la interfase ICloneable.

La forma de implementa el metodo Clone de la interfase ICloneable es mediante el uso de la serializacion:
        public object Clone()
        {
            MemoryStream buffer = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
           
            formatter.Serialize(buffer, this);
            buffer.Position = 0;

            object temp = formatter.Deserialize(buffer);
            return temp;
        }

Donde el objeto regresado es una copia en memoria e independiente del original, aquí es donde comenzaron mis problemas con el método Clone, la serializacion y los eventos. Resulta que mi objeto que implementa la interfase ICloneable expone un evento, y cuando intente sacar una copiar mi objeto  la parte donde se realiza la deserializacion fallaba horriblemente con una excepción:
The serialize method works at it should be, but when I try to deserialize the buffer, it fails with the following exception:
System.Runtime.Serialization.SerializationException was unhandled
  Message="Cannot get the member 'Child_PropertyChanged'."
  Source="mscorlib"

El motivo del error en la deserializacion se debe a que el BinaryFormatter serializa y deserializa todas las variables declaradas a nivel de la clase (fields) que no estén marcadas con el atributo NonSerialized; el problema se genera si el objeto a serializar contiene la declaración de un delegado (EventHandler) o la declaración de un evento, y estos no están marcados como NonSerialized.

El motivo por el cual se tienen que marcar la declaración del evento y delegado (EventHandler) como NonSerializable para que el proceso de deserilizacion no falle se debe a que, cuando un objeto se subscribe para recibir la notificación de un evento de otro objeto, el objeto que genera el evento mantiene una lista de a quien debe de notificar, por lo tanto si el objeto que genera el evento es serializado, al momento de ser deserializado es posible que que los objetos que estaban subscritos ya no existan, ya que hay que recordar que la serializacion se puede utilizar para guardar un objeto a disco, a una base de datos; o bien transmitirlo remotamente con un WebService o Remoting; o como en mi caso serializarlo a memoria para después recrearlo.

Para la declaracion de un delegado (EventHandler), simplemente con colocar el atributo NonSerialized es suficiente para que BinaryFormatter no trate de serializarlo, en el caso de la declaración de un evento el atributo NonSerializable no puede colocarse directamente en la declaración del evento:
        [NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
Ya que el atributo esta restringido para ser usado solo sobre campos (fields), la forma en como se tiene que colocar, es especificando que atributo es para el campo que declara el evento, no para el evento en si mismo:
        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

Después de colocar el atributo, todo funciono a la perfección, pude utilizar el método Clone para crear una copia de mi objeto original.

No hay comentarios.: