使用 IXmlSerializable 接口序列化 Hashtable、集合和泛型






4.82/5 (8投票s)
本文展示了如何使用 XmlSerializer 来序列化通常无法 XML 序列化的类型。
引言
本文讨论如何通过实现 IXmlSerializer
接口来序列化通常无法用 XmlSerializer
序列化的类。本文将展示 3 种不同的实现。
背景
我通常需要将对象序列化到 XML 文件中,以保存配置和您可以想象的任何内容。我厌倦了 XmlSerialization
的限制,大家都一样,我搜索了 CodeProject 来找出如何做到这一点。有很多关于这些问题的文章。您可以使用二进制序列化 Hashtable
,但这并非 XML。您可以通过手动逐个元素进行序列化来序列化所有您想要的东西,但如果您需要为大型或复杂的类执行此操作,这可能会是大量工作。
最后,我发现了 IXmlSerializable
接口。它允许修改 XmlSerializer
的行为。到目前为止,我在 CodeProject 上找不到关于这个接口的任何内容,而 CodeProject 是我的参考。因此,我决定写这篇文章。在本文中,我将只讨论 XmlSerializer
,以及如何通过它使用 IXmlSerializable
接口。
IXmlSerializer
当您使用 XmlSerializer
序列化对象时,有两种情况:第一种(一般情况)对象不实现 IXmlSerializable
接口,XmlSerializer
使用反射来读写 XML 文件。第二种情况是您的对象实现了 IXmlSerializable
,并且代替反射,会调用 IXmlSerializable.WriteXml
和 IXmlSerializable.ReeadXml
方法。
public interface IXmlSerializable
{
XmlSchema GetSchema();
void ReadXml(XmlReader reader);
void WriteXml(XmlWriter writer);
}
第一个实现示例:HashtableSerializable
此示例来自 Matt Berther。这是我找到的最好也是最简单的示例,感谢 Matt。这是代码类,非常简单。
public class HashtableSerializable : Hashtable, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema(){ ... }
public void ReadXml(System.Xml.XmlReader reader){ ... }
public void WriteXml(System.Xml.XmlWriter writer){ ... }
}
我不需要获取模式,本文不是关于这个实现的。请看 WriteXml
方法。
public void WriteXml(System.Xml.XmlWriter writer)
{
// Write the root element
writer.WriteStartElement("dictionary");
// Foreach object in this (as i am a Hashtable)
foreach(object key in this.Keys)
{
object value = this[key];
// Write item, key and value
writer.WriteStartElement("item");
writer.WriteElementString("key", key.ToString());
writer.WriteElementString("value", value.ToString());
// write </item>
writer.WriteEndElement();
}
// write </dictionary>
writer.WriteEndElement();
}
}
我们只需写入类似以下的 XML:
<dictionary>
<item>
<key>...</key>
<value>...</value>
</item>
</dictionary>
现在我们必须重新读取它。请现在看一下 ReadXml
方法。当调用的 XmlSerialize
调用它时,reader 参数的位置正好在我们所需的位置。在返回之前,请注意将其放在正确的位置,以便 XmlSerializer
可以继续反序列化。
public void ReadXml(System.Xml.XmlReader reader)
{
// Start to use the reader.
reader.Read();
// Read the first element i.e. root of this object
reader.ReadStartElement("dictionary");
// Read all elements
while(reader.NodeType != XmlNodeType.EndElement)
{
// parsing the item
reader.ReadStartElement("item");
// Parsing the key and value
string key = reader.ReadElementString("key");
string value = reader.ReadElementString("value");
// end reading the item.
reader.ReadEndElement();
reader.MoveToContent();
// add the item
this.Add(key, value);
}
// Extremely important to read the node to its end.
// next call of the reader methods will crash if not called.
reader.ReadEndElement();
}
请特别注意最后的 reader.ReadEndElement()
,它会消耗最后一个 </dictionary>
,并将 reader 位置置于末尾。
就是这样。现在,如果我需要序列化 Class1
的成员 Hashtable
,我只需要使用 HashtableSerializable
即可。
第二个实现示例:覆盖 XmlSerialiser,当它已经在工作时
从历史上看,我有一个 ByteArray
类,它允许我操作字节。这是一个来自 Framework 1.1 的旧类,它实现了 CollectionBase
(它在 Framework 2 中编译)。这个类实现了 Icollection
、IEnumerable
,并且可以隐式地从 byte[]
转换而来或转换为 byte[]
。因此,它可以使用 XmlSerializable
进行序列化,结果如下:
<ArrayOfUnsignedByte
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<unsignedByte>1</unsignedByte>
<unsignedByte>2</unsignedByte>
<unsignedByte>3</unsignedByte>
<unsignedByte>4</unsignedByte>
<unsignedByte>5</unsignedByte>
<unsignedByte>6</unsignedByte>
<unsignedByte>7</unsignedByte>
<unsignedByte>8</unsignedByte>
<unsignedByte>9</unsignedByte>
<unsignedByte>10</unsignedByte>
<unsignedByte>11</unsignedByte>
<unsignedByte>12</unsignedByte>
<unsignedByte>13</unsignedByte>
<unsignedByte>14</unsignedByte>
<unsignedByte>15</unsignedByte>
</ArrayOfUnsignedByte>
很好,但我希望得到一个更易读的结果,并以十六进制书写。那么您猜怎么着?我实现了 IXmlSerializable
!
(ByteArray.Parse(string hexa)
解析像 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
这样的字符串,并将其转换为 ByteArray
,而 ByteArray.ToHexString()
将其序列化为字符串。)
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
string temp = reader.ReadString();
try
{
this.Add(ByteArray.Parse(temp));
}
catch{}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteString(this.ToHexString());
}
现在我这样序列化:
<?xml version="1.0" encoding="utf-8"?>
<ByteArray>01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F</ByteArray>
第三个实现示例:泛型
首先,通常我会在我的类中使用很多 Lists<>
,它们序列化得非常好。当然,如果列表中的对象不可序列化,它就不会被序列化。在这个示例中,我有一个 Set<T>
类,它是使用 CodeProject 现有的 Set
文章创建的。
- Theo Bebekis 的另一个 C# Set 类
- PIEBALDconsult 的 Set 类
- ‘RalfW’ 基于枚举的 C# Set 类
- Scott Mitchell 的 Pascal Set
- Jason Smith 为 .NET 添加对 ‘Set’ 集合的支持
- Keith Barrett 的 C 语言 Set 集合
我的类是这些类的混合体。它围绕 Hashtable
构建,并且不可序列化,这对我来说是一个真正的问题。正是这个类引导我了解了 IXmlSerializable
,并最终写下了这篇文章。我不会讨论 Set
类本身,请参阅前面的文章,我并没有用它创建任何新东西。请注意,它是一个泛型类。我只是添加了 IXmlSerializable
实现。
public class Set<T> : ICollection<T>, IEnumerable, IXmlSerializable
, IListSource
{
/// <summary>
/// Read an XML element corresponding to a Set of T
/// </summary>
/// <param name="reader"></param>
void IXmlSerializable.ReadXml(XmlReader reader)
{
// this line supposes that T is XmlSerializable
// (not an IDictionnary for example, or implements IXmlSerializable too ...)
System.Xml.Serialization.XmlSerializer S =
new System.Xml.Serialization.XmlSerializer(typeof(T));
// Very important
reader.Read();
while(reader.NodeType != XmlNodeType.EndElement)
{
try
{
// YES it uses the XmlSerializer to serialize each item!
// It is so simple.
T item = (T)S.Deserialize(reader);
if(item != null) // May be I have to throw here ?
this.Add(item);
}
catch
{
// May be i have to throw ??
}
}
// Very important, if reader.Read()
reader.ReadEndElement();
}
/// <summary>
/// Write an XML Element corresponding to a Set of T
/// </summary>
/// <param name="writer"></param>
void IXmlSerializable.WriteXml(XmlWriter writer)
{
// this line supposes that T is XML Serializable
// (not an IDictionnary for example, or implements IXmlSerializable too ...)
System.Xml.Serialization.XmlSerializer S =
new System.Xml.Serialization.XmlSerializer(typeof(T));
foreach(T item in this.InternalHashtable.Values)
{
try
{
// YES it uses the XmlSerializer to serialize each item !
// It is so simple.
S.Serialize(writer, item, null);
}
catch//(Exception ex)
{
// May be I have to throw ??
// System.Windows.Forms.MessageBox.Show(ex.ToString());
// writer.WriteElementString(this._TypeName, null);
}
}
}
}
正如您所看到的,实现很奇怪,因为它也使用了 XmlSerializer
(不要忘记,正是 XmlSerializer
调用这两个方法来读写 XML)。
关注点
我的 ByteArray
类似乎缺少了一些东西,当我有很多字节时,我无法正确地缩进我的数据。使用此方法我总是只得到一行。我成功地制作了很多行,但缩进总是很糟糕。请告诉我如何做到这一点……
历史
- 2008 年 6 月 18 日:首次发布
将来可能还会有新版本……