XML反序列化时如何忽略类构造函数的执行?
XML反序列化时如何避免构造函数初始化列表?
我有一个包含泛型列表List(Of DocumentType)的类,在构造函数中给这个列表添加了几个预设实例作为初始值。用户可以在UI里修改这个列表,比如清空所有元素后把类序列化成XML。但之后从XML反序列化时,构造函数会被调用,列表又被重新填充默认值,可实际应该保持为空。
奇怪的是:其他属性能从XML正确赋值,但列表不行;如果不在构造函数里初始化列表,一切就正常。
以下是我的类代码:
Imports System.ComponentModel Public Class ArticleAndBOMExportEntrySettings Implements INotifyPropertyChanged Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs) RaiseEvent PropertyChanged(Me, e) End Sub Sub New() Me.ExtensionsArticle = New List(Of DocumentType)({New DocumentType() With { .Documenttype = DocumentType.EDocumentType.a, .Extension = $".{DocumentType.EDocumentType.a}", .Name = DocumentType.EDocumentType.a.ToString.ToUpper}, New DocumentType() With { .Documenttype = DocumentType.EDocumentType.a, .Extension = $".{DocumentType.EDocumentType.a}", .Name = DocumentType.EDocumentType.a.ToString.ToUpper}}) Me.DbUserArticle = "Test" End Sub 'This is set on deserialze Public Property DbUserArticle As String = "" 'This has always the value set from the constructor, also after deserialize from XML Why? Public Property ExtensionsArticle As List(Of DocumentType) End Class <Serializable> Public Class DocumentType Implements IEquatable(Of DocumentType) Enum EDocumentType a b c End Enum <Browsable(False)> Property Documenttype As EDocumentType = EDocumentType.a Public Property Name As String = "" <Browsable(False)> Property Extension As String = $".{Documenttype}" Public Shadows Function Equals(other As DocumentType) As Boolean Implements IEquatable(Of DocumentType).Equals Return Me.Name = other.Name End Function End Class
解决方案
核心原因说明
.NET的XML反序列化一定会调用无参构造函数,而集合属性是引用类型:构造函数中创建的列表实例会被保留,反序列化时只会尝试往这个实例中添加XML里的元素(如果有的话)。如果XML里是空列表,反序列化不会修改这个实例,所以你看到的还是构造函数里的预设值。而字符串这类值类型/不可变引用类型,反序列化会直接覆盖构造函数的赋值,所以表现正常。
1. 延迟初始化集合(推荐简单方案)
把集合的预设初始化逻辑移到属性的Get访问器中,仅在集合未被初始化时填充默认值。这样反序列化时,XML中的空列表会直接赋值给属性字段,不会触发默认值填充:
Imports System.ComponentModel Public Class ArticleAndBOMExportEntrySettings Implements INotifyPropertyChanged Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs) RaiseEvent PropertyChanged(Me, e) End Sub Private _extensionsArticle As List(Of DocumentType) Sub New() Me.DbUserArticle = "Test" ' 移除构造函数中的列表初始化 End Sub Public Property DbUserArticle As String = "" Public Property ExtensionsArticle As List(Of DocumentType) Get If _extensionsArticle Is Nothing Then ' 仅首次获取且未初始化时填充预设值 _extensionsArticle = New List(Of DocumentType)({ New DocumentType() With { .Documenttype = DocumentType.EDocumentType.a, .Extension = $".{DocumentType.EDocumentType.a}", .Name = DocumentType.EDocumentType.a.ToString.ToUpper}, New DocumentType() With { .Documenttype = DocumentType.EDocumentType.a, .Extension = $".{DocumentType.EDocumentType.a}", .Name = DocumentType.EDocumentType.a.ToString.ToUpper}}) End If Return _extensionsArticle End Set _extensionsArticle = value OnPropertyChanged(New PropertyChangedEventArgs(NameOf(ExtensionsArticle))) End Set End Property End Class
2. 实现IXmlSerializable接口(完全自定义控制)
如果需要更精细地控制序列化/反序列化流程,可以实现IXmlSerializable接口,手动处理所有属性的读写,彻底绕开构造函数对集合的影响:
Imports System.ComponentModel Imports System.Xml Imports System.Xml.Schema Public Class ArticleAndBOMExportEntrySettings Implements INotifyPropertyChanged, IXmlSerializable Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs) RaiseEvent PropertyChanged(Me, e) End Sub Sub New() Me.DbUserArticle = "Test" End Sub Public Property DbUserArticle As String = "" Public Property ExtensionsArticle As List(Of DocumentType) ' IXmlSerializable接口实现 Public Function GetSchema() As XmlSchema Implements IXmlSerializable.GetSchema Return Nothing End Function Public Sub ReadXml(reader As XmlReader) Implements IXmlSerializable.ReadXml reader.ReadStartElement() ' 读取DbUserArticle If reader.Name = NameOf(DbUserArticle) Then DbUserArticle = reader.ReadElementContentAsString() End If ' 读取ExtensionsArticle If reader.Name = NameOf(ExtensionsArticle) Then ExtensionsArticle = New List(Of DocumentType)() reader.ReadStartElement() While reader.NodeType <> XmlNodeType.EndElement Dim docType = New DocumentType() ' 手动读取DocumentType的属性 docType.Documenttype = CType([Enum].Parse(GetType(DocumentType.EDocumentType), reader.GetAttribute("Documenttype")), DocumentType.EDocumentType) docType.Name = reader.GetAttribute("Name") docType.Extension = reader.GetAttribute("Extension") ExtensionsArticle.Add(docType) reader.Read() End While reader.ReadEndElement() End If reader.ReadEndElement() End Sub Public Sub WriteXml(writer As XmlWriter) Implements IXmlSerializable.WriteXml ' 写入DbUserArticle writer.WriteElementString(NameOf(DbUserArticle), DbUserArticle) ' 写入ExtensionsArticle writer.WriteStartElement(NameOf(ExtensionsArticle)) For Each docType In ExtensionsArticle writer.WriteStartElement("DocumentType") writer.WriteAttributeString("Documenttype", docType.Documenttype.ToString()) writer.WriteAttributeString("Name", docType.Name) writer.WriteAttributeString("Extension", docType.Extension) writer.WriteEndElement() Next writer.WriteEndElement() End Sub End Class
内容的提问来源于stack exchange,提问作者gegy
相关产品推荐
相关产品推荐

