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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (8投票s)

2008 年 6 月 18 日

CPOL

4分钟阅读

viewsIcon

79120

downloadIcon

850

本文展示了如何使用 XmlSerializer 来序列化通常无法 XML 序列化的类型。

引言

本文讨论如何通过实现 IXmlSerializer 接口来序列化通常无法用 XmlSerializer 序列化的类。本文将展示 3 种不同的实现。

背景

我通常需要将对象序列化到 XML 文件中,以保存配置和您可以想象的任何内容。我厌倦了 XmlSerialization 的限制,大家都一样,我搜索了 CodeProject 来找出如何做到这一点。有很多关于这些问题的文章。您可以使用二进制序列化 Hashtable,但这并非 XML。您可以通过手动逐个元素进行序列化来序列化所有您想要的东西,但如果您需要为大型或复杂的类执行此操作,这可能会是大量工作。

最后,我发现了 IXmlSerializable 接口。它允许修改 XmlSerializer 的行为。到目前为止,我在 CodeProject 上找不到关于这个接口的任何内容,而 CodeProject 是我的参考。因此,我决定写这篇文章。在本文中,我将只讨论 XmlSerializer,以及如何通过它使用 IXmlSerializable 接口。

IXmlSerializer

当您使用 XmlSerializer 序列化对象时,有两种情况:第一种(一般情况)对象不实现 IXmlSerializable 接口,XmlSerializer 使用反射来读写 XML 文件。第二种情况是您的对象实现了 IXmlSerializable,并且代替反射,会调用 IXmlSerializable.WriteXmlIXmlSerializable.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 中编译)。这个类实现了 IcollectionIEnumerable,并且可以隐式地从 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 文章创建的。

  1. Theo Bebekis 的另一个 C# Set 类
  2. PIEBALDconsult 的 Set 类
  3. ‘RalfW’ 基于枚举的 C# Set 类
  4. Scott Mitchell 的 Pascal Set
  5. Jason Smith 为 .NET 添加对 ‘Set’ 集合的支持
  6. 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 日:首次发布
    将来可能还会有新版本……
© . All rights reserved.