You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 11:27:32