DataContractSerializer 不调用我的构造函数?

我刚刚意识到一些疯狂的事情,我认为这是完全不可能的: 当反序列化一个对象时,DataContractSerializer 不调用构造函数

以这门课为例:

[DataContract]
public class Book
{
public Book()
{ // breakpoint here
}


[DataMember(Order = 0)]
public string Title { get; set; }
[DataMember(Order = 1)]
public string Author { get; set; }
[DataMember(Order = 2)]
public string Summary { get; set; }
}

当我反序列化该类的一个对象时,不会命中断点。我完全不知道这怎么可能,因为它是这个对象的唯一构造函数!

我假设由于 DataContract属性,编译器可能生成了一个额外的构造函数,但是我无法通过反射找到它..。

因此,我想知道的是: 如何在不调用构造函数的情况下创建类的实例?

注意: 我知道我可以使用 OnDeserializing属性在反序列化开始时初始化我的对象,这不是我的问题的主题。

17140 次浏览

DataContractSerializer (like BinaryFormatter) doesn't use any constructor. It creates the object as empty memory.

For example:

    Type type = typeof(Customer);
object obj = System.Runtime.Serialization.
FormatterServices.GetUninitializedObject(type);

The assumption is that the deserialization process (or callbacks if necessary) will fully initialize it.

There are some scenario's that wouldn’t be possible without this behavior. Think of the following:

1) You have an object that has one constructor that sets the new instance to an "initialized" state. Then some methods are called on that instance, that bring it in a "processed" state. You don’t want to create new objects having the "processed" state, but you still want de serialize / deserialize the instance.

2) You created a class with a private constructor and some static properties to control a small set of allowed constructor parameters. Now you can still serialize / deserialize them.

XmlSerializer has the behavior you expected. I have had a some problems with the XmlSerializer because it DOES need a default constructor. Related to that, sometimes it makes sense to have private property setters. But the XmlSerializer also needs public getter and setter on properties in order to serialize / deserialize.

I think of the DataContractSerializer / BinaryFormatter behavior like suspending the state of an instance during serialization and resuming during deserialization. In other words, the instances are not “constructed” but “restored” to an earlier state.

As you already mentioned, the [OnDeserializing] attribute makes it possible to keep non serialized data in sync.

Use [OnDeserialized] attribute to initialise your properties.

// This method is called after the object
// is completely deserialized. Use it instead of the
// constructror.
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
fullName = firstName + " " + lastName;
}

Please refer to microsoft guid-lines: https://learn.microsoft.com/en-us/dotnet/standard/serialization/serialization-guidelines

In my case, i wanted to create an object to use in a lock-clause. I tried implementing IDeserializationCallback (didn't work because callback only runs after properties have been assigned), [OnDeserialized] (didn't work, same previous reason), and ISerializable (didn't work because the class is decorated with the [DataContractAttribute]).

My workaround was to initialize the field before it's used using Interlocked.CompareExchange. A bit of unnecessary work gets done, but at least now my field gets initialized when a DataContractSerializer creates it.

Interlocked.CompareExchange(ref _sync, new object(), null);

FWIW, you can call the constructor explicitly from a [OnDeserializing] method:

[OnDeserializing]
public void OnDeserializing(StreamingContext context)
{
this.GetType().GetConstructor(System.Array.Empty<Type>()).Invoke(this, null);
}