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

XMLSerializer 输入/输出实用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (10投票s)

2009 年 5 月 13 日

MIT

5分钟阅读

viewsIcon

35540

downloadIcon

333

一篇关于使用 XMLSerializer 进行序列化和反序列化的文章。

引言

在我开始在 C++ 中使用 XML 文档时,解析和从中提取数据并不像我所说的那么友好,从头开始生成新文档也是如此。与此同时,我也开始接触一门叫做 C# 的新语言。在某个时候,我正在用 C# 进行 XML 解析,并发现了 XML 序列化功能 (XMLSerializer)。Needless to say,这对我来说是一个很好的解决方案,因为我经常处理内存中的对象关系,并且会花费大量的代码在两者之间进行转换。

随着时间的推移,我发现自己编写了一套实用函数,用于将 XML 文档加载和保存到不同的格式(文件、字符串、内存流等)。有一天我意识到,我可以使用泛型来使这个过程更容易,而不用一遍又一遍地复制、粘贴和修改同一个方法模板。

XML 序列化背景

本文不打算详细解释 XML 序列化是如何工作的(这些是可能在未来文章中讨论的想法)。但是,我将介绍一个非常简单的“Hello World”类,它将用于本示例。基本的 XML 文档如下所示:

<?xml version="1.0"?>
<hello>
  <message>Hello World</message>
</hello>

我们用于序列化的 C# 类如下所示:

namespace BackgroundCode
{
    public class Hello
    {
        public string Message { get; set; }
    }
}

这非常直接,事实上,它看起来就像一个普通的 C# 对象。神奇之处就在这里;假设我们想从上面的 XML 文档中读取内容,您可以使用以下代码:

public static Hello ReadDocument(string fileName)
{
    XmlSerializer xs = new XmlSerializer(typeof(Hello));
    using (Stream stream = File.OpenRead(fileName))
    {
        return xs.Deserialize(stream) as Hello;
    }
}

现在,您可能会注意到这里有很多地方可能出错,任何调用此函数的人都应该预料到可能会从 File.OpenReadxs.Deserialize 中收到异常。您也可以编写一个类似的函数来写入文件:

public static void WriteDocument(Hello xml, string fileName)
{
    XmlSerializer xs = new XmlSerializer(typeof(Hello));
    using (Stream stream = File.OpenWrite(fileName))
    {
        xs.Serialize(stream, xml);
    }
}

迭代 1

实际读取和写入文档的工作似乎很容易,但作为 DRY(Don't Repeat Yourself,不要重复自己)的倡导者,似乎最合乎逻辑的地方是将此功能放在数据定义类本身中。我更新后的类如下所示:

using System.IO;
using System.Xml.Serialization;

namespace Iteration1
{
    public class Hello
    {
        public string Message { get; set; }

        public static Hello ReadDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Hello));
            using (Stream stream = File.OpenRead(fileName))
            {
                return xs.Deserialize(stream) as Hello;
            }
        }

        public void WriteDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Hello));
            using (Stream stream = File.OpenWrite(fileName))
            {
                xs.Serialize(stream, this);
            }
        }
    }
}

与我们在背景部分的内容相比,有一些小的更改,以及几点需要注意。首先要注意的是,ReadDocument 是一个静态函数。这允许您使用一个巧妙的技巧来加载您的文档:

Hello read = Hello.ReadDocument(fname);

下一个要注意的是 WriteDocument,它现在只接受文件名,而不是 Hello 对象的实例。这是因为我们可以直接使用关键字“this”来写入文档。保存它的调用看起来像这样:

Hello data = new Hello { Message = "Hello World" };
data.WriteDocument(fname);

好了,很长一段时间以来,我都会在这里结束我的工作。毕竟,当我对一个新类需要这样做时,我只需要复制粘贴这些函数并修改它们。现在,让我向您展示这种说法的谬误。假设我们有一个新类(这是一个“goodbye world”类)。所以,我们从这样的内容开始:

namespace Iteration1
{
    public class Goodbye
    {
        public string Reason { get; set; }
    }
}

现在,我们复制我们的实用函数并进行修改:

using System.IO;
using System.Xml.Serialization;

namespace Iteration1
{
    public class Goodbye
    {
        public string Reason { get; set; }

        public static Goodbye ReadDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Goodbye));
            using (Stream stream = File.OpenRead(fileName))
            {
                return xs.Deserialize(stream) as Goodbye;
            }
        }

        public void WriteDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Hello));
            using (Stream stream = File.OpenWrite(fileName))
            {
                xs.Serialize(stream, this);
            }
        }
    }
}

您看出错误了吗?这不会导致编译错误,只要您只从文件中读取,您可能很长时间都不会注意到这个错误,直到您最终要进行保存时才收到类似这样的异常:

"There was an error generating the XML document."

迭代 2

这里关键词是“重构”,现在我们引入泛型来帮助我们。作为我们第二个示例的逻辑扩展,让我们看看这个解决方案的下一个迭代,使用我们的第一个 XMLSupport 类。首先,我们恢复到 Hello.cs 的初始实现:

现在,我们引入了实用类:

using System.IO;
using System.Xml.Serialization;

namespace Iteration2
{
    public sealed class XMLUtility
    {
        public static T ReadDocument<t>(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (Stream stream = File.OpenRead(fileName))
            {
                return (T) xs.Deserialize(stream);
            }
        }

        public static void WriteDocument<t>(T xmlObject, string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (Stream stream = File.OpenWrite(fileName))
            {
                xs.Serialize(stream, xmlObject);
            }
        }
    }
}

您现在可以使用如下语句从文件读取:

Hello read = XMLUtility.ReadDocument<hello>(fname);

您可以使用如下语句写入文件:

Hello data = new Hello { Message = "Hello World" };
XMLUtility.WriteDocument<hello>(data, fname);

迭代 3

虽然上一个解决方案(经过一些增强)是一个不错的进步(并且在许多情况下可能是正确的解决方案),但我认为我们还可以做得更多。与使用具有泛型函数的类相反,我们引入了一个泛型类:

using System.IO;
using System.Xml.Serialization;

namespace Iteration3
{
    public sealed class XMLUtility<t>
    {
        public static T ReadDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (Stream stream = File.OpenRead(fileName))
            {
                return (T)xs.Deserialize(stream);
            }
        }

        public static void WriteDocument(T xmlObject, string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (Stream stream = File.OpenWrite(fileName))
            {
                xs.Serialize(stream, xmlObject);
            }
        }
    }
}

老实说,这还没有让事情好多少,因为调用仍然看起来像:

Hello data = new Hello { Message = "Hello World" };
XMLUtility<hello>.WriteDocument(data, fname);

Hello read = XMLUtility<hello>.ReadDocument(fname);

迭代 4

既然我们已经抽象到了一个泛型类,让我们将一些继承引入其中。首先,是实用类:

using System;
using System.IO;
using System.Xml.Serialization;

namespace Iteration4
{
    public abstract class XMLSupport<t>
    {
        public static T ReadDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (Stream stream = File.OpenRead(fileName))
            {
                return (T)xs.Deserialize(stream);
            }
        }

        public void WriteDocument(string fileName)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (Stream stream = File.OpenWrite(fileName))
            {
                xs.Serialize(stream, this);
            }
        }
    }
}

现在,让我们看看我们的数据类:

namespace Iteration4
{
    public class Hello : XMLSupport<hello>
    {
        public string Message { get; set; }
    }
}

所以这使得读取调用看起来像:

Hello read = Hello.ReadDocument(fname);

以及写入调用看起来像:

Hello data = new Hello { Message = "Hello World" };
data.WriteDocument(fname);

与之前的迭代相比,这大大提高了可读性和易用性。

其他功能

XMLSupport 的最终版本增加了新的功能,并且也包含了一些名称更改。因为有时您需要读取和写入字符串和通用流(而不是文件),所以现在有三个读取操作和三个写入操作:

读取 Write
FromXmlString ToXmlString
FromStream ToStream
FromFile ToFile

出于其他高级原因,添加了两个事件:BeforeSaveAfterLoad。它们是泛型事件处理程序 (EventHandler<eventargs>),您可以向其中添加自己的事件。

Using the Code

使用此库非常简单。查看包含的测试代码,您可以看到一个 Hello 类:

public class Hello : XMLSupport<hello>
{
    public Hello()
    {
        BeforeSave += BeforeSaveEvent;
        AfterLoad += AfterLoadEvent;
    }

    public string Message { get; set; }

    [XmlIgnore]
    public bool beforeSaveCalled = false;

    [XmlIgnore]
    public bool afterLoadCalled = false;

    protected void BeforeSaveEvent(object sender, EventArgs args)
    {
        beforeSaveCalled = true;
    }

    protected void AfterLoadEvent(object sender, EventArgs args)
    {
        afterLoadCalled = true;
    }
}

此类别在测试目的下利用了 BeforeSaveAfterLoad 事件,但提供了使用它的示例。

这是一个使用上述类从字符串读取的简单示例:

Hello ret = Hello.FromXmlString("<hello><message>Hello World</message></hello>");

这些函数在很大程度上与迭代 4 中描述的一样工作,只是方法名称略有不同。

结论

Microsoft 的 XmlSerializer 是处理 XML 文件时一个极其有用的实用程序。通过使用 C# 和泛型的某些高级功能,可以使添加加载和保存支持成为一个非常简单的操作。使用 XMLSupport 库可以使这个过程非常简单。

历史

  • 首次修订。
© . All rights reserved.