序列化和反序列化 IEnumerable 对象
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
属性将从基于ICollection
、IList
、ArrayList
、BitArray
、CollectionBase
、DictionaryBase
、HashTable
或SortedList
对象的抽象类中公开。Enqueue
和Peek
方法将从基于Queue
对象的抽象类中公开。Push
和Peek
方法将从基于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 反序列化为对象。写操作将对象序列化为各种目标介质。读操作从各种源介质中反序列化对象。
属性 | 描述 |
---|---|
CDataStorage |
将所有字符串值序列化为 XML CData 标签。 |
IncludeClassNameAttribute |
记录类名进行序列化,以确保类可以被反序列化。 |
IgnoreWarnings |
忽略警告,允许在遇到不支持的类型时继续反序列化。 |
方法 |
定义如何序列化/反序列化类。支持以下值:SerializationMethod.Shallow (默认)或 SerializationMethod.Deep 。 |
方法 | 参数 | 描述 |
---|---|---|
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*)。该示例建模旨在展示序列化器如何处理对象关系的多个层级。
Buyer
对象必须实现 System.XML.Serialization.IXmlSerializable
接口,因为图中的某些类类型无法由 System.XML.Serialization.XmlSerializer
序列化。通常,需要大量工作来支持该接口。但是,使用 CustomXmlSerializer
,我们可以将数百行复杂的代码减少到只有四行代码。
<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
类并加载默认值。
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*所示。
'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")
'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
可以序列化和反序列化数组值。
<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*所示。
_Person = New Buyer
Dim _textStreamReader As New System.IO.StringReader(_xmlText.ToString)
Dim _xmlReader As New System.Xml.XmlTextReader(_textStreamReader)
_Person.ReadXml(_xmlReader)
_Person = New Buyer
Dim _XMLReader As New CustomXmlSerializer
_Person = _XMLReader.ReadXml(_xmlText, _Person)
关注点
从反射的角度来看,序列化类一直很简单,直到泛型出现。从一开始,反序列化就很难实现。
我在识别哪个程序集包含需要实例化的类时遇到了问题。为了解决这个问题,我使用了父类的程序集。因此,所有需要反序列化的类都需要位于同一个程序集中。
此外,字典支持键/值对。为了将键与值关联起来,序列化器将键记录为值元素标签的属性。
历史
日期 | 版本号 | 变更历史 |
---|---|---|
06/17/2006 | 1.0 |
VB 版本的初始发布。
|
06/30/2006 | 1.1 |
Bug 修复(详情请参阅内联“Fix”注释)
新功能
|
07/06/2006 | 1.2 |
|
07/13/2006 | 1.3 |
|
08/01/2006 | 1.4 |
|
12/09/2006 | 1.5 |
|
12/12/2006 | 1.6 |
|