XMLSerializer 输入/输出实用程序






4.17/5 (10投票s)
一篇关于使用 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.OpenRead
和 xs.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 |
出于其他高级原因,添加了两个事件:BeforeSave
和 AfterLoad
。它们是泛型事件处理程序 (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;
}
}
此类别在测试目的下利用了 BeforeSave
和 AfterLoad
事件,但提供了使用它的示例。
这是一个使用上述类从字符串读取的简单示例:
Hello ret = Hello.FromXmlString("<hello><message>Hello World</message></hello>");
这些函数在很大程度上与迭代 4 中描述的一样工作,只是方法名称略有不同。
结论
Microsoft 的 XmlSerializer
是处理 XML 文件时一个极其有用的实用程序。通过使用 C# 和泛型的某些高级功能,可以使添加加载和保存支持成为一个非常简单的操作。使用 XMLSupport 库可以使这个过程非常简单。
历史
- 首次修订。