BinaryFormatter.Deserialize 如何创建新对象?
当 BinaryFormatter 将流反序列化为对象时,它似乎创建了新对象而不调用构造函数。
它是如何做到这一点的?为什么? .NET 中还有其他东西可以做到这一点吗?
这是一个演示:
[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.");
}
}
}
输出:
建造的汽车:1
但实际上有两个。
When BinaryFormatter
deserializes a stream into objects, it appears to create new objects without calling constructors.
How is it doing this? And why? Is there anything else in .NET that does this?
Here's a demo:
[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.");
}
}
}
Output:
Cars constructed: 1
But there are actually two.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
调用构造函数可以做(或者至少应该做)两件事。
一种是为该对象留出一定量的内存,并执行所有必要的内务处理,使其成为 .NET 世界其他部分的对象(请注意本说明中的一定量的手动操作)。
另一种是将对象置于有效的初始状态,可能基于参数 - 这就是构造函数中的实际代码将执行的操作。
反序列化通过调用 FormatterServices.GetUninitializedObject 执行与第一步相同的操作,然后通过将字段的值设置为与序列化期间记录的值相同,执行与第二步相同的操作(这可能需要反序列化其他对象来表示值)。
现在,反序列化将对象放入的状态可能与任何构造函数可能不对应的状态。最好的情况是这是浪费(构造函数设置的所有值都将被覆盖),最坏的情况可能是危险的(构造函数有一些副作用)。这也可能是不可能的(只有构造函数是接受参数的构造函数 - 序列化无法知道要使用什么参数)。
您可以将其视为一种仅由反序列化使用的特殊构造函数(OO 纯粹主义者将会并且应该对不构造的构造函数的想法感到不寒而栗,我的意思是这只是一个类比,如果您知道 C++ 认为就记忆而言,覆盖
new
的工作方式,你有一个更好的类比,尽管仍然只是一个类比)。现在,在某些情况下这可能是一个问题 - 也许我们有只能由构造函数设置的只读字段,或者我们可能有我们想要的副作用发生。
解决这两个问题的方法是使用
ISerialized
覆盖序列化行为。这将基于对的调用进行序列化ISerialized.GetObjectData
,然后使用SerializationInfo
和StreamingContext
字段调用特定构造函数来反序列化(所述构造函数甚至可以是私有的 - 这意味着大多数其他代码甚至不会看到它)。因此,如果我们可以反序列化 readonly 字段并产生我们想要的任何副作用(我们也可以做各种事情来控制序列化的内容和方式)。如果我们只关心确保在构造时发生反序列化时发生一些副作用,我们可以实现 IDeserializationCallback ,并且在反序列化完成时调用 IDeserializationCallback.OnDeserialization 。
至于其他与此相同的事情,.NET 中还有其他形式的序列化,但这就是我所知道的。可以自己调用 FormatterServices.GetUninitializedObject ,但除非您有强有力的保证,后续代码会将生成的对象置于有效状态(即反序列化时您所处的情况)序列化同类对象所生成的数据中的对象)这样做是令人担忧的,并且是产生非常难以诊断的错误的好方法。
There are two things calling a constructor does (or at least should do).
One is to set aside a certain amount of memory for the object and does all the housekeeping necessary for it to be an object to the rest of the .NET world (note certain amount of handwaving in this explanation).
The other is to put the object into a valid initial state, perhaps based on parameters - this is what the actual code in the constructor will do.
Deserialisation does much the same thing as the first step by calling
FormatterServices.GetUninitializedObject
, and then does much the same thing as the second step by setting the values for fields to be equivalent to those that were recorded during serialisation (which may require deserialising other objects to be said values).Now, the state that deserialisation is putting the object into may not correspond to that possible by any constructor. At best it will be wasteful (all values set by the constructor will be overwritten) and at worse it could be dangerous (constructor has some side-effect). It could also just be impossible (only constructor is one that takes parameters - serialisation has no way of knowing what arguments to use).
You could look at it as a special sort of constructor only used by deserialisation (OO purists will - and should - shudder at the idea of a constructor that doesn't construct, I mean this as an analogy only, if you know C++ think of the way overriding
new
works as far as memory goes and you've an even better analogy, though still just an analogy).Now, this can be a problem in some cases - maybe we have
readonly
fields that can only be set by a constructor, or maybe we have side-effects that we want to happen.A solution to both is to override serialisation behaviour with
ISerializable
. This will serialise based on a call toISerializable.GetObjectData
and then call a particular constructor withSerializationInfo
andStreamingContext
fields to deserialise (said constructor can even be private - meaning most other code won't even see it). Hence if we can deserialisereadonly
fields and have any side-effects we want (we can also do all manner of things to control just what is serialised and how).If we just care about ensuring some side-effect happens on deserialisation that would happen on construction, we can implement
IDeserializationCallback
and we will haveIDeserializationCallback.OnDeserialization
called when deserialisation is complete.As for other things that do the same thing as this, there are other forms of serialisation in .NET but that's all I know of. It is possible to call
FormatterServices.GetUninitializedObject
yourself but barring a case where you have a strong guarantee that subsequent code will put the object produced into a valid state (i.e. precisely the sort of situation you are in when deserialising an object from data produced by serialising the same sort of object) doing such is fraught and a good way to produce a really hard to diagnose bug.问题是,BinaryFormatter 并没有真正创建您的特定对象。它将对象图放回到内存中。对象图基本上是对象在内存中的表示;这是在对象序列化时创建的。然后,反序列化调用基本上只是将该图作为打开指针处的对象粘回内存中,然后通过代码将其转换为实际情况。如果转换错误,则会引发异常。
至于你的具体例子,你实际上只是在制造一辆汽车;你只是在制作那辆车的精确复制品。当您将其序列化到流中时,您将存储它的精确二进制副本。当你反序列化它时,你不需要构造任何东西。它只是将图形作为对象粘贴在内存中的某个指针值处,并让您可以用它做任何您想做的事情。
由于指针位置不同,您对 car1 != car2 的比较是正确的,因为 Car 是引用类型。
为什么?坦率地说,提取二进制表示很容易,而不必提取每个属性等等。
我不确定 .NET 中的其他内容是否使用相同的过程;最有可能的候选者是在序列化期间以某种格式使用对象的二进制文件的任何其他东西。
The thing is, BinaryFormatter isn't really making your particular object. It's putting an object graph back into memory. The object graph is basically the representation of your object in memory; this was created when the object is serialized. Then, the deserialize call basically just sticks that graph back in memory as an object at an open pointer, and then it gets casted to what it actually is by the code. If it's casted wrong, then an exception is thrown.
As to your particular example, you are only really constructing one car; you are just making an exact duplicate of that car. When you serialize it off into the stream, you store an exact binary copy of it. When you deserialize it, you don't have to construct anything. It just sticks the graph in memory at some pointer value as an object and lets you do whatever you want with it.
Your comparison of car1 != car2 is true because of that different pointer location, since Car is a reference type.
Why? Frankly, it's easy to just go pull the binary representation, rather than having to go and pull each property and all that.
I'm not sure whether anything else in .NET uses this same procedure; the most likely candidates would be anything else that uses an object's binary in some format during serialization.
不知道为什么构造函数没有被调用,但我使用 < code>IDeserializationCallback 作为解决方法。
另请查看
OnSerializingAttribute
OnSerializedAttribute
< /a>OnDeserializingAttribute
OnDeserializedAttribute
Not sure why the constructor does not get called but I use
IDeserializationCallback
as a work around.also take a look at
OnSerializingAttribute
OnSerializedAttribute
OnDeserializingAttribute
OnDeserializedAttribute