DataContractSerializer 不调用我的构造函数?

Posted

技术标签:

【中文标题】DataContractSerializer 不调用我的构造函数?【英文标题】:DataContractSerializer doesn't call my constructor? 【发布时间】:2010-11-07 18:48:51 【问题描述】:

我刚刚意识到一些疯狂的事情,我认为这是完全不可能的:反序列化对象时,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 属性来初始化我的对象,这不是我的问题。

【问题讨论】:

或“OnDeserialized”,当对象完成反序列化后,填写缺失的字段。 我也想到了这个问题:***.com/questions/178645/… 【参考方案1】:

DataContractSerializer(如 BinaryFormatter)不使用 any 构造函数。它将对象创建为空内存。

例如:

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

假设反​​序列化过程(或必要时的回调)将完全初始化它。

【讨论】:

反序列化时根本不应该调用构造函数。如果它被调用,那么 1) 你在构造函数中创建的资源怎么样?他们会泄漏! 2)您将对象初始化两次,一次在构造函数中,一次在反序列化值中。 @Dodu a:这不会导致泄漏 b:(调用 ctor)是许多序列化程序的工作方式;任何一种方式通常都可以作为日志,因为行为被理解并且设计者相应地 我只是不确定为什么会需要这个功能。构造函数用于状态初始化。绕过它根本没有任何目的。无论是否要反序列化,都应该允许无参数构造函数设置状态。如果他们想要特定的优化,可以使用 StreamingContext 构造函数。 @Mranz 反序列化也可以描述为状态初始化,并且简单地说:并非所有类型都有无参数构造函数。在我自己的序列化程序中,我将其设为可选并默认关闭(仅限选择加入) 我明白为什么这可能是需要的。您正在反序列化一个对象。序列化的对象应该是一个完整的表示。【参考方案2】:

如果没有这种行为,有些场景是不可能实现的。想一想:

1) 您有一个对象,该对象具有一个构造函数,该构造函数将新实例设置为“已初始化”状态。然后在该实例上调用一些方法,使其处于“已处理”状态。您不想创建具有“已处理”状态的新对象,但仍需要反序列化/反序列化实例。

2) 您创建了一个具有私有构造函数和一些静态属性的类,以控制一小组允许的构造函数参数。现在你仍然可以序列化/反序列化它们。

XmlSerializer 具有您预期的行为。我在使用 XmlSerializer 时遇到了一些问题,因为它确实需要一个默认构造函数。与此相关的是,有时拥有私有财产设置者是有意义的。但是 XmlSerializer 还需要属性上的公共 getter 和 setter 才能序列化/反序列化。

我想到了 DataContractSerializer / BinaryFormatter 行为,例如在序列化期间暂停实例的状态并在反序列化期间恢复。换句话说,实例不是“构造”而是“恢复”到更早的状态。

正如您已经提到的,[OnDeserializing] 属性可以使非序列化数据保持同步。

【讨论】:

我不会说它“暂停实例的状态”,因为我刚刚遇到了一个问题,即我的 DataMember 正在使用 INotifyPropertyChanged 并且未调用构造函数时,它会触发 NotifyPropertyChanged 所以行为不会被暂停。【参考方案3】:

FWIW,您可以从 [OnDeserializing] 方法显式调用构造函数:

[OnDeserializing]
public void OnDeserializing(StreamingContext context)

    this.GetType().GetConstructor(System.Array.Empty<Type>()).Invoke(this, null);

【讨论】:

【参考方案4】:

使用 [OnDeserialized] 属性来初始化您的属性。

// This method is called after the object
// is completely deserialized. Use it instead of the
// constructror.
[OnDeserialized]
void OnDeserialized(StreamingContext context)

    fullName = firstName + " " + lastName;

请参考微软指南: https://docs.microsoft.com/en-us/dotnet/standard/serialization/serialization-guidelines

【讨论】:

【参考方案5】:

就我而言,我想创建一个对象以在锁定子句中使用。我尝试实现 IDeserializationCallback(不起作用,因为回调仅在分配属性后运行 )、[OnDeserialized](不起作用,与之前的原因相同)和 ISerializable(不起作用,因为类用 [DataContractAttribute] 修饰)。

我的解决方法是在使用 Interlocked.CompareExchange 之前初始化该字段。完成了一些不必要的工作,但至少现在我的字段在 DataContractSerializer 创建它时被初始化。

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

【讨论】:

您可能想尝试 [OnDeserializing],它发生在 开始填充对象之前

以上是关于DataContractSerializer 不调用我的构造函数?的主要内容,如果未能解决你的问题,请参考以下文章

使用 DataContractSerializer 时与 XmlArrayItem 等效的是啥? [复制]

DataContractSerializer 不调用我的构造函数?

使用 DataContractSerializer 自定义序列化

DataContractSerializer - 如何将 xml 输出到字符串(而不是文件)

通过 DataContractSerializer 跳过 DataMember

使用 DataContractSerializer 时设置属性的初始值