c# - ¿Cómo crea BinaryFormatter.Deserialize nuevos objetos?
constructor serialization (3)
Hay dos cosas que llama a un constructor (o al menos debería hacerlo).
Una es dejar de lado cierta cantidad de memoria para el objeto y hacer todo el mantenimiento necesario para que sea un objeto para el resto del mundo .NET (tenga en cuenta cierta cantidad de handwaving en esta explicación).
La otra es poner el objeto en un estado inicial válido, quizás basado en parámetros, esto es lo que hará el código real en el constructor.
La deserialización hace lo mismo que el primer paso llamando a FormatterServices.GetUninitializedObject
, y luego hace lo mismo que el segundo paso al configurar los valores para que los campos sean equivalentes a los que se registraron durante la serialización (lo que puede requerir la deserialización de otros objetos). para ser dichos valores).
Ahora, el estado en el que la deserialización está poniendo el objeto puede no corresponder a lo posible por parte de cualquier constructor. En el mejor de los casos, será un desperdicio (todos los valores establecidos por el constructor se sobrescribirán) y, en el peor de los casos, podría ser peligroso (el constructor tiene algún efecto secundario). También podría ser simplemente imposible (solo constructor es uno que toma parámetros, la serialización no tiene forma de saber qué argumentos usar).
Podría verlo como un tipo especial de constructor que solo se usa en la deserialización (los puristas de OO se estremecerán, y deberían, temblar ante la idea de un constructor que no construye, quiero decir esto como una analogía solamente, si sabe que C ++ piensa en la forma en que se anulan los trabajos new
en cuanto a memoria va y se tiene una analogía aún mejor, aunque todavía es solo una analogía).
Ahora, esto puede ser un problema en algunos casos, tal vez tengamos campos de solo readonly
que solo puede establecer un constructor, o tal vez tengamos efectos secundarios que queremos que ocurran.
Una solución para ambos es anular el comportamiento de serialización con ISerializable
. Esto se serializará en función de una llamada a ISerializable.GetObjectData
y luego llamará a un constructor particular con los campos SerializationInfo
y StreamingContext
para deserializar (dicho constructor puede ser privado, lo que significa que la mayoría de los demás códigos no lo verán). Por lo tanto, si podemos deserializar los campos de readonly
y tener los efectos secundarios que deseamos (también podemos hacer todo tipo de cosas para controlar qué se serializa y cómo).
Si solo nos preocupa garantizar que ocurran algunos efectos secundarios en la deserialización que ocurrirían en la construcción, podemos implementar IDeserializationCallback
y tendremos IDeserializationCallback.OnDeserialization
llama cuando se completa 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 a FormatterServices.GetUninitializedObject
usted mismo, salvo un caso en el que tenga una garantía sólida de que el código subsiguiente pondrá al objeto producido en un estado válido (es decir, exactamente el tipo de situación en que se encuentra al deserializar un objeto de los datos producidos por la serialización el mismo tipo de objeto) hacer esto es difícil y una buena manera de producir un error realmente difícil de diagnosticar.
Cuando BinaryFormatter
deserializa un flujo en objetos, parece crear nuevos objetos sin llamar a los constructores.
¿Cómo está haciendo esto? ¿Y por qué? ¿Hay algo más en .NET que haga esto?
Aquí hay 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.
La cuestión es que BinaryFormatter no está realmente creando tu objeto en particular. Es poner un gráfico de objetos de nuevo 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 se serializa. Luego, la llamada de deserialización básicamente simplemente pega ese gráfico en la memoria como un objeto en un puntero abierto, y luego se convierte en lo que realmente es por el código. Si está mal fundido, entonces se lanza una excepción.
En cuanto a tu ejemplo particular, solo estás realmente construyendo un auto; Usted está haciendo un duplicado exacto de ese coche. Cuando lo serializa en el flujo, almacena una copia binaria exacta de él. Cuando lo deserializa, no tienes que construir nada. Simplemente guarda el gráfico en la memoria en algún valor de puntero como un objeto y le permite hacer lo que quiera con él.
Tu 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 simplemente ir a la representación binaria, en lugar de tener que ir y tirar de cada propiedad y todo eso.
No estoy seguro de si alguna otra cosa en .NET usa este mismo procedimiento; los candidatos más probables serían cualquier otra cosa que utilice el binario de un objeto en algún formato durante la serialización.
No estoy seguro de por qué no se llama al constructor, pero uso IDeserializationCallback
como una IDeserializationCallback
.
también echar un vistazo a