65.9K
CodeProject 正在变化。 阅读更多。
Home

序列化和反序列化 IEnumerable 对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (29投票s)

2006年6月17日

CPOL

10分钟阅读

viewsIcon

405478

downloadIcon

2438

CustomXmlSerializer 是 XmlSerializer 的一种替代方案,它支持 ArrayList、Collection 和 Dictionary 的浅层和深层序列化。

引言

将对象中的数据转换为 XML 的过程称为序列化。序列化的反向过程,即将 XML 加载到对象中,称为反序列化。浅层序列化将*公共*属性和字段转换为 XML,而深层序列化则将*公共和私有*成员都转换为 XML。System.Xml.Serialization.XmlSerializer 用于执行浅层序列化。

尽管我喜欢使用 XmlSerializer,但它的局限性足以让人抓狂。XmlSerializer 不支持 System.Collections 命名空间中定义的大部分类型。我很少编写需要序列化的代码而不使用一种或多种集合类型!厌倦了这些限制,我终于花时间创建了一个名为 CustomXmlSerializer 的自定义序列化器。本文演示了如何使用 CustomXmlSerializer 来序列化/反序列化各种 IEnumerable 列表类型。

背景

如果您只对序列化 CollectionBase 抽象类感兴趣,那么您可能想查看文章“序列化 CollectionBase 的自定义集合”。

功能

CustomXmlSerializer 旨在支持任何对象的序列化/反序列化。

CustomXmlSerializer 专门设计用于支持以下 IEnumerable 数组类型:

  • System.Array
  • System.Collections.ArrayList
  • System.Collections.BitArray
  • System.Collections.CollectionBase
  • System.Collections.DictionaryBase
  • System.Collections.Hashtable
  • System.Collections.Queue
  • System.Collections.SortedList
  • System.Collections.Stack

System.Collections.Generic 中定义的类型有有限支持。

为了支持 System.Collections 命名空间的反序列化和序列化,CustomXmlSerializer 做了以下假设:

  • Add 方法和 Item 属性将从基于 ICollectionIListArrayListBitArrayCollectionBaseDictionaryBaseHashTableSortedList 对象的抽象类中公开。
  • EnqueuePeek 方法将从基于 Queue 对象的抽象类中公开。
  • PushPeek 方法将从基于 Stack 对象的抽象类中公开。

限制

IDictionary

IncludeClassNameAttribute 设置为 False 时,CustomXmlSerializer 使用反射来尝试检测将添加到集合中的变量类型。实现 IDictionary 接口的类始终返回 DictionaryEntry 对象,该对象的值始终为 Object 类型。

为了让 CustomXmlSerializer 正确反序列化 XML,它必须知道要实例化的数据类型。类名提供了实例化正确对象的说明。当使用任何实现 IDictionary 接口的集合时,在序列化期间将 IncludeClassNameAttribute 属性设置为 True

队列

IDictionary 类一样,序列化 Queue 对象时,必须将 IncludeClassNameAttribute 设置为 True

结构体

CustomXmlSerializer 无法反序列化结构。结构是装箱的,这使得它们非常难以反序列化。如果需要反序列化,请使用类而不是结构。

Generics

CustomXmlSerializer 对泛型提供有限的支持。CustomXmlSerializer 可以序列化抽象泛型类,但不能序列化基泛型类。例如,System.Collections.Generic.Dictionary 不能作为变量序列化。但是,如果创建一个继承自 System.Collections.Generic.Dictionary 的抽象类,则序列化和反序列化都可以正常工作。

从序列化的角度来看,最好使用标准对象作为数据类。需要序列化的类应该是定义良好的类。虽然泛型使我们能够创建类型安全的、可重用的类,但我不会认为它们是定义良好的。泛型存储的数据类型在运行时才正式确定,从泛型的角度来看,每次实例化都可以定义为不同的类型。

泛型的使用对于辅助类更有意义,在这些类中您需要使用相同的代码支持不同的类型。我之所以得出这个结论,是因为我没有找到任何好的方法来序列化引用其他泛型的泛型。使用泛型数据对象迫使开发人员使用 IXmlSerializable 接口来支持序列化。我迄今为止找到的关于序列化泛型的最佳文章是 DevX 的“在 .NET 2.0 中序列化、消耗和绑定泛型”以及 Microsoft 的“C# 泛型简介”。

在我收到有关此主题的抱怨邮件之前,我并不是说您不应该使用泛型。泛型的概念是 VS.NET 开发人员武器库中一个强大的新功能。我只是说,不要因为可以就变得疯狂地使用泛型。如果您需要支持序列化,最好避免使用泛型类。

CustomXmlSerializer 成员

CustomXmlSerializer 将对象序列化为 XML,并将 XML 反序列化为对象。写操作将对象序列化为各种目标介质。读操作从各种源介质中反序列化对象。

图 1:CustomXmlSerializer 类图

Figure 1: CustomXMLSerializer Class Diagram

表 1:CustomXmlSerializer 属性成员
属性 描述
CDataStorage 将所有字符串值序列化为 XML CData 标签。
IncludeClassNameAttribute 记录类名进行序列化,以确保类可以被反序列化。
IgnoreWarnings 忽略警告,允许在遇到不支持的类型时继续反序列化。
方法 定义如何序列化/反序列化类。支持以下值:SerializationMethod.Shallow(默认)或 SerializationMethod.Deep
表 2:CustomXmlSerializer 方法成员
方法 参数 描述
ReadXML 重载例程。 将 XML 反序列化到目标对象。
ByVal reader As System.Xml.XmlReader, ByVal target As Object 将 XML 从指定的读取器加载到指定的目标中。
ByVal node As System.Xml.XmlNode, ByVal target As Object 将 XML 从指定的 XmlNode 加载到指定的目标中。
ByVal document As System.Xml.XmlDocument, ByVal target As Object 将 XML 从指定的文档加载到指定的目标中。
ByVal path As String, ByVal target As Object 将 XML 从指定的文件加载到指定的目标中。
ByVal text As System.Text.StringBuilder, ByVal target As Object 将 XML 从指定的 StringBuilder 加载到指定的目标中。
WriteDocument ByVal source As Object 将源对象序列化为 XmlDocument,遵循“浅层复制”业务逻辑。
WriteFile ByVal source As Object, ByVal path As String, Optional ByVal replaceFile As Boolean = False 将源对象序列化到文件,遵循“浅层复制”业务逻辑。
WriteString ByVal source As Object 将源对象序列化为字符串,遵循“浅层复制”业务逻辑。
WriteText ByVal source As Object 将源对象序列化为 StringBuilder,遵循“浅层复制”业务逻辑。
WriteXML ByVal source As Object, ByVal writer As System.Xml.XmlWriter, Optional ByVal propertyName As String = Nothing 将源对象序列化为 XmlWriter,遵循“浅层复制”业务逻辑。

使用 CustomXmlSerializer

以下示例演示了如何创建可序列化数据对象以供 CustomXmlSerializer 使用 Web 服务。

示例数据对象模型

创建了几个数据对象来演示 CustomXmlSerializer 的工作原理(参见*图 2*)。该示例建模旨在展示序列化器如何处理对象关系的多个层级。

图 2:示例数据对象类图

Figure 2: Example Data Objects Class Diagram

Buyer 对象必须实现 System.XML.Serialization.IXmlSerializable 接口,因为图中的某些类类型无法由 System.XML.Serialization.XmlSerializer 序列化。通常,需要大量工作来支持该接口。但是,使用 CustomXmlSerializer,我们可以将数百行复杂的代码减少到只有四行代码。

列表 1:支持 IXmlSerializable
<Serializable()> Public Class Buyer_
      Implements System.Xml.Serialization.IXmlSerializable

'Download Demo to View Class Properties

#Region "Implements IXmlSerializable"
    Public Function GetSchema() As System.Xml.Schema.XmlSchema _
    Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) _
    Implements System.Xml.Serialization.IXmlSerializable.ReadXml
        Dim _XMLReader As New CustomXmlSerializer
        _XMLReader.ReadXML(reader, Me)
    End Sub

    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) _
    Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        Dim _XMLWriter As New CustomXmlSerializer
        _XMLWriter.WriteXML(Me, writer)
    End Sub
#End Region

加载对象模型

在能够演示序列化功能之前,必须创建一个 Buyer 类并加载默认值。

列表 2:加载示例数据对象
Private Function CreateBuyer() As Buyer
    Dim _Person As New Buyer

    _Person.Identifier = 24
    _Person.Name = "Joe Schmoe"
    _Person.EMailAddress = "JoeSchmoe@FutureHomeOwners.com"
    _Person.PhoneNumber = "666-777-8888"

    Dim _Question As Profile.Question

    _Question = New Profile.Question
    _Question.Identifier = 1
    _Question.Text = "Target Price Range"
    _Question.Tip = "Select the price range that you" & _ 
                    " are able/willing to pay for a new home"
    _Question.Type = QuestionTypes.SelectOne
    _Question.Options = New System.Collections.ArrayList
    _Question.Options.Add("$200,000 - $250,000")
    _Question.Options.Add("$250,000 - $300,000")
    _Question.Options.Add("$300,000 - $500,000")
    _Question.Options.Add("$500,000 or More")
    _Question.OtherOptions = New System.Collections.ArrayList
    _Question.OtherOptions.Add("Test")
    _Question.OtherOptions.Add(CType(1, Integer))
    _Question.OtherOptions.Add(CType(1.1, Double))
    ReDim _Question.TestArray(2)
    _Question.TestArray(0) = "Test Array 1"
    _Question.TestArray(1) = "Test Array 2"

    _Person.Questionnaire.Questions.Add(_Question)

    _Question = New Profile.Question
    _Question.Identifier = 2
    _Question.Text = "Building Style"
    _Question.Tip = "Identify the styles that you like"
    _Question.Type = QuestionTypes.SelectMany
    _Question.Options = New System.Collections.ArrayList
    _Question.Options.Add("Modern")
    _Question.Options.Add("Gothic")
    _Question.Options.Add("European")
    _Question.Options.Add("Spanish")
    _Question.Options.Add("Colonial")
    
    _Person.Questionnaire.Questions.Add(_Question)

    _Question = New Profile.Question
    _Question.Identifier = 3
    _Question.Text = "Special Requests"
    _Question.Tip = "Document any additional needs/wants that we should consider"
    _Question.Type = QuestionTypes.Text
    
    _Person.Questionnaire.Questions.Add(_Question)

    Return _Person
End Function

将对象模型序列化为 XML

加载的 Buyer 对象随后被序列化为 StringBuilder,并显示结果。您可以选择手动序列化类,如*列表 3*所示,或者使用辅助方法 WriteText,如*列表 4*所示。

列表 3:手动序列化数据对象
'Instantiate and Load the Buyer Object
Dim _Person As Buyer = CreateBuyer()

'Serialize the Buyer Object into XML
Dim _xmlText As New System.Text.StringBuilder
Dim _TextStreamWriter As New System.IO.StringWriter(_xmlText)
Dim _xmlWriter As New System.Xml.XmlTextWriter(_TextStreamWriter)
_Person.WriteXml(_xmlWriter)

'Display the XML Results
MsgBox(_xmlText.ToString, MsgBoxStyle.OKOnly, "Serialization Completed")
列表 4:使用辅助例程序列化数据对象
'Instantiate and Load the Buyer Object
Dim _Person As Buyer = CreateBuyer()

'Serialize the Buyer Object into XML

Dim _XMLWriter As New CustomXmlSerializer
Dim _xmlText As New System.Text.StringBuilder = _XmlWriter.WriteText(_Person)

'Display the XML Results
MsgBox(_xmlText.ToString, MsgBoxStyle.OKOnly, "Serialization Completed")

运行示例时,您会注意到缺少回车符和空格。CustomXmlSerializer 会排除这些字符以节省空间。为了方便阅读,回车符和空格已手动添加到下面的示例输出中。

在生成的输出中,您可以看到所有类和属性都变成了元素,其值包含在元素标签内。CustomXmlSerializer 使用属性来识别如何将数据反序列化回目标对象。反序列化逻辑使用的属性之一是 className 属性。

className 用于帮助反序列化器了解要实例化的对象。当同一列表中存储了不同的值类型时,才需要 className 属性。OtherOptions 标签显示了一个需要 className 的示例,因为存在各种子类类型。

如果省略 className 属性,反序列化器将尝试根据第一个子元素的元素标签名来实例化子类。使用这种方法,反序列化器期望所有子类都是同一类型。Options 标签代表了一个在子元素中无需类名的示例。

TestArray 元素显示了如何序列化字符串变量数组(System.Array 类型)。这表明 CustomXmlSerializer 可以序列化和反序列化数组值。

列表 5:序列化数据对象的示例输出
<Buyer className="Example.Buyer">
  <Identifier>24</Identifier>
  <Name>Joe Schmoe</Name>
  <EMailAddress>JoeSchmoe@FutureHomeOwners.com</EMailAddress>
  <PhoneNumber>666-777-8888</PhoneNumber>
  <Questionnaire className="Example.Profile.Questionnaire">
    <Questions className="Example.Profile.Questions">
      <Question className="Example.Profile.Question">
        <OtherOptions className="System.Collections.ArrayList">
          <String className="System.String">Test</String>
          <Int32 className="System.Int32">1</Int32>
          <Double className="System.Double">1.1</Double>
        </OtherOptions>
        <TestArray size="3" className="System.Array" type="System.String">
          <System.Array.Item point="0">
            <String className="System.String">Test Array 1</String>
          </System.Array.Item>
          <System.Array.Item point="1">
            <String className="System.String">Test Array 2</String>
          </System.Array.Item>
        </TestArray>
        <Identifier>1</Identifier>
        <Text>Target Price Range</Text>
        <Tip>Select the price range that you are able/willing to pay for a new home</Tip>
        <Type>SelectOne</Type>
        <Options className="System.Collections.ArrayList">
          <String className="System.String">$200,000 - $250,000</String>
          <String className="System.String">$250,000 - $300,000</String>
          <String className="System.String">$300,000 - $500,000</String>
          <String className="System.String">$500,000 or More</String>
        </Options>
        <TestArray2 className="System.Collections.ArrayList">
          <String className="System.String">Test</String>
          <Int32 className="System.Int32">1</Int32>
          <Double className="System.Double">1.1</Double>
        </TestArray2>
    </Question>
      <Question className="Example.Profile.Question">
        <OtherOptions />
        <TestArray size="1" className="System.Array" type="System.String">
          <System.Array.Item point="0">
            <String className="System.String" />
          </System.Array.Item>
        </TestArray>
        <Identifier>2</Identifier>
        <Text>Building Style</Text>
        <Tip>Identify the styles that you like</Tip>
        <Type>SelectMany</Type>
        <Options className="System.Collections.ArrayList">
          <String className="System.String">Modern</String>
          <String className="System.String">Gothic</String>
          <String className="System.String">European</String>
          <String className="System.String">Spanish</String>
          <String className="System.String">Colonial</String>
        </Options>
        <TestArray2 />
      </Question>
      <Question className="Example.Profile.Question">
        <OtherOptions />
        <TestArray size="1" className="System.Array" type="System.String">
          <System.Array.Item point="0">
            <String className="System.String" />
          </System.Array.Item>
        </TestArray>
        <Identifier>3</Identifier>
        <Text>Special Requests</Text>
        <Tip>Document any additional needs/wants that we should consider</Tip>
        <Type>Text</Type>
        <Options />
        <TestArray2 />
      </Question>
    </Questions>
    <Responses className="Example.Profile.Responses" />
    <Identifier>0</Identifier>
    <DateCreated>1/1/0001 12:00:00 AM</DateCreated>
    <DateModified>1/1/0001 12:00:00 AM</DateModified>
    <State>Created</State>
    <ChangeCount>0</ChangeCount>
  </Questionnaire>
  <PreferredPlans />
  <SelectedPlan />
</Buyer>

将 XML 反序列化到对象模型

为了将 XML 反序列化回 Buyer 对象,我们创建一个新的 Buyer。接下来,XML 字符串值被读回 buyer。您可以手动反序列化对象,如*列表 6*所示,或者使用辅助例程 ReadXml,如*列表 7*所示。

列表 6:手动将 XML 反序列化回示例数据对象
_Person = New Buyer

Dim _textStreamReader As New System.IO.StringReader(_xmlText.ToString)
Dim _xmlReader As New System.Xml.XmlTextReader(_textStreamReader)
_Person.ReadXml(_xmlReader)
列表 7:使用辅助例程将 XML 反序列化回示例数据对象
_Person = New Buyer
Dim _XMLReader As New CustomXmlSerializer
_Person = _XMLReader.ReadXml(_xmlText, _Person)

关注点

从反射的角度来看,序列化类一直很简单,直到泛型出现。从一开始,反序列化就很难实现。

我在识别哪个程序集包含需要实例化的类时遇到了问题。为了解决这个问题,我使用了父类的程序集。因此,所有需要反序列化的类都需要位于同一个程序集中。

此外,字典支持键/值对。为了将键与值关联起来,序列化器将键记录为值元素标签的属性。

历史

日期 版本号 变更历史
06/17/2006 1.0

VB 版本的初始发布。

  • 支持所有 System.Collection 列表类型的序列化和反序列化。
  • 支持 System.Array 的序列化。
06/30/2006 1.1

Bug 修复(详情请参阅内联“Fix”注释)

  • 不再分配 Nothing/null 值。为某些数据类型分配 Nothing/null 值不可靠。
  • 添加了代码以验证是否反序列化了 IEnumerable

新功能

  • C# 版本的初始发布。
  • 支持自定义 VB.NET 和 C# 数据类型。
  • 如果类定义匹配,则支持 C# [与/从] VB.NET 进行[序列化/反序列化]。
  • 对 VB.NET 版本进行代码更改,以简化转换为 C#,并继续在两种语言中进行维护。
  • 除了浅层序列化外,现在通过 Method 成员支持深层序列化。
  • 支持 2003 和 2005,无需任何代码更改。
  • 支持 VS 2003 [与/从] VS 2005 进行反序列化。
07/06/2006 1.2
  • 添加了反序列化 System.Array 类型的能力。
07/13/2006 1.3
  • 修复了与 System.Array 序列化/反序列化相关的一些错误。
  • 添加了对序列化/反序列化 IEnumerable 子类的支持。
08/01/2006 1.4
  • 修复了将 VB 代码转换为 C# 时出现的问题。
  • C# 版本中的 SaveValue 例程已修改。特别感谢一位朋友指出了问题!
12/09/2006 1.5
  • 修复了反序列化实现 IDictionary 接口的集合时出现的问题。
  • 添加了对 XmlIgnoreAttribute 属性的支持。
12/12/2006 1.6
  • 修复了反序列化 Queue 集合时出现的问题。
© . All rights reserved.