2010-08-17 11 views
18

Cuando BinaryFormatter deserializa una secuencia en objetos, parece crear nuevos objetos sin llamar a constructores.¿Cómo crea BinaryFormatter.Deserialize nuevos objetos?

¿Cómo está esto? ¿Y por qué? ¿Hay algo más en .NET que hace esto?

He aquí una demostración:

[Serializable] 
public class Car 
{ 
    public static int constructionCount = 0; 

    public Car() 
    { 
     constructionCount++; 
    } 
} 

public class Test 
{ 
    public static void Main(string[] args) 
    { 
     // Construct a car 
     Car car1 = new Car(); 

     // Serialize and then deserialize to create a second, identical car 
     MemoryStream stream = new MemoryStream(); 
     BinaryFormatter formatter = new BinaryFormatter(); 
     formatter.Serialize(stream, car1); 
     stream.Seek(0, SeekOrigin.Begin); 
     Car car2 = (Car)formatter.Deserialize(stream); 

     // Wait, what happened? 
     Console.WriteLine("Cars constructed: " + Car.constructionCount); 
     if (car2 != null && car2 != car1) 
     { 
      Console.WriteLine("But there are actually two."); 
     } 
    } 
} 

Salida:

Cars constructed: 1
But there are actually two.

+0

Buena pregunta. Para solucionar este problema, deberá realizar algunas correcciones de puntero/referencia durante la deserialización, lo que puede ser difícil o incluso imposible. Tenga en cuenta el hecho de que 'nuevo coche' solo se llamó una vez. Es posible que desee probar esto en 2 procesos. – leppie

+0

posible duplicado de [DataContractSerializer no llama a mi constructor ??] (http://stackoverflow.com/questions/1076730/datacontractserializer-doesnt-call-my-constructor) –

+2

Nota: La otra pregunta a la que me he vinculado es sobre DataContractSerializer , pero la explicación es la misma para BinaryFormatter –

Respuesta

3

La cosa es, BinaryFormatter no está realmente haciendo su objeto particular. Está volviendo a poner un gráfico de objetos en la memoria. El gráfico de objetos es básicamente la representación de su objeto en la memoria; esto fue creado cuando el objeto es serializado. Entonces, la llamada de deserialización simplemente deja ese gráfico atrás en la memoria como un objeto en un puntero abierto, y luego se convierte en lo que realmente es por el código. Si se lanza incorrectamente, se lanza una excepción.

En cuanto a su ejemplo particular, usted solo está realmente construyendo un automóvil; solo estás haciendo un duplicado exacto de ese auto. Cuando lo serializa en la transmisión, almacena una copia binaria exacta de la misma. Cuando lo deserializas, no tienes que construir nada. Simplemente pega el gráfico en la memoria en algún valor de puntero como un objeto y le permite hacer lo que quiera con él.

Su comparación de car1! = Car2 es verdadera debido a esa ubicación diferente del puntero, ya que Car es un tipo de referencia.

¿Por qué? Francamente, es fácil ir tirando de la representación binaria, en lugar de tener que ir y sacar cada propiedad y todo eso.

No estoy seguro de si algo más en .NET utiliza este mismo procedimiento; los candidatos más probables serían cualquier cosa que use el binario de un objeto en algún formato durante la serialización.

17

Hay dos cosas que un constructor llama (o al menos debería hacer).

Una es reservar una cierta cantidad de memoria para el objeto y todo el mantenimiento necesario para que sea un objeto para el resto del mundo .NET (tenga en cuenta cierta cantidad de movimientos manuales en esta explicación).

La otra es poner el objeto en un estado inicial válido, tal vez en función de los parámetros; esto es lo que hará el código real en el constructor.

La deserialización hace más o menos lo mismo que el primer paso llamando al FormatterServices.GetUninitializedObject, y luego hace más o menos lo mismo que el segundo paso estableciendo los valores de los campos equivalentes a los que se grabaron durante la serialización (que pueden requerir deserialización otros objetos para ser dichos valores).

Ahora, el estado en el que la deserialización está metiendo el objeto puede no corresponderse con el posible por cualquier constructor. En el mejor de los casos será un desperdicio (todos los valores establecidos por el constructor serán sobrescritos) y en el peor podría ser peligroso (el constructor tiene algún efecto secundario). También podría ser simplemente imposible (solo el constructor es el que toma los parámetros; la serialización no tiene forma de saber qué argumentos usar).

Podrías verlo como un tipo especial de constructor solo utilizado por la deserialización (los puristas de OO -y deberían- se estremecen ante la idea de un constructor que no construye, me refiero a esto como una analogía solamente, si Saber C++ pensar en la forma de anular new funciona en la medida de la memoria y tienes una analogía aún mejor, aunque sigue siendo una analogía).

Ahora, esto puede ser un problema en algunos casos - tal vez tenemos readonly campos que sólo pueden resolverse de un constructor, o tal vez tienen efectos secundarios que queremos suceda.

Una solución para ambos es anular el comportamiento de serialización con ISerializable. Esto se serializará según una llamada al ISerializable.GetObjectData y luego llamará a un constructor particular con los campos SerializationInfo y StreamingContext para deserializar (dicho constructor puede incluso ser privado, lo que significa que la mayoría de los demás códigos ni siquiera lo verán). Por lo tanto, si podemos deserializar los campos readonly y tener los efectos secundarios que deseamos (también podemos hacer todo tipo de cosas para controlar solo lo que se serializa y cómo).

Si solo nos preocupamos de garantizar que ocurra algún efecto secundario en la deserialización que ocurriría en la construcción, podemos implementar IDeserializationCallback y tendremos que llamar a IDeserializationCallback.OnDeserialization cuando se complete la deserialización.

En cuanto a otras cosas que hacen lo mismo que esto, hay otras formas de serialización en .NET, pero eso es todo lo que sé. Es posible llamar usted mismo al FormatterServices.GetUninitializedObject, salvo que tenga una fuerte garantía de que el código posterior colocará el objeto producido en un estado válido (es decir, exactamente el tipo de situación en la que se encuentra al deserializar un objeto a partir de datos producidos por serialización del mismo tipo de objeto) haciendo esto es precario y una buena manera de producir un error muy difícil de diagnosticar.

+1

+1 - IDeserializationCallback es una gran idea. Úselo para inicializar los campos privados necesarios, etc. ¡Resolvió mi problema! – womp

Cuestiones relacionadas