### .NET 使 XML 串行化易如反掌

人们一直高喊 XML 是解决系统互联问题的关键, 而.NET framework 也为处理 XML 数据提供了许多不同的类库. XmlDocument 类能让你像处理文件一样处理 XML 数据, 而 XmlReader, XmlWriter, 和它们的派生类使你能够将 XML 数据做为数据流处理. XmlSerializer 则提供了另外的方法, 它使你能够将自己的对象串行和反串行化为 XML. 串行化数据既能够让你像处理文件一样对数据进行随机存取, 同时又能够跳过你不感兴趣的元素. 在本文中, 我将向你展示如何使用 XmlSerializer 类以及如何在你的类中添加属性来控制串行化过程.
XmlSerializer
XmlSerializer 类存在于 System.Xml.Serialization 命名空间的 System.Xml.dll 中, 它用一种高度松散耦合的方式提供串行化服务. 你的类不需要继承特别的基类, 而且它们也不需要实现任何特别的接口. 相反的, 你只需要在你的类或者这些类的公共域以及读/写属性里加上自定义的属性. XmlSerializer 通过相反映射读取这些属性并用它们将你的类和类成员映射到 XML 元素和属性.
将 XML 映射到对象
考虑表 A 中的 XML 语句, 哪一个正确的描述了一家电影院中上映的电影呢?
表 A
<?xml version="1.0" encoding="utf-8" ?>
<theater>
<name>The Camelot</name>
<phone>(888)665-2222</phone>
<movie minutes="120" stars="2">
<title>The Score</title>
<rating>R</rating>
<showing>16:15:00</showing>
<showing>19:05:00</showing>
<showing>21:40:00</showing>
</movie>
<movie minutes="100">
<title>Shrek</title>
<rating>PG-13</rating>
<showing>16:00:00</showing>
<showing>19:00:00</showing>
<showing>21:40:00</showing>
</movie>
</theater>
表 B 中定义了一个 Theater(电影院)类, 它包含了 XmlSerializer 使用的属性映射.
表 B
using System;
using System.Xml.Serialization;
namespace Articles.TechRepublic.XmlSerialization
{
[XmlRoot( "theater" )]
public class Theater
{
[XmlElement( "name" )]
public string Name = "";
[XmlElement( "phone" )]
public string Phone = "";
[XmlElement( "movie" )]
public Movie[] Movies;
public override string ToString()
{
string movies = "";
if ( Movies != null )
foreach ( Movie movie in Movies )
movies += "\n" + movie.ToString();
return String.Format( "{0}\n {1}\n{2}",
Name, Phone, movies );
}
}
XmlRoot 属性将类 Theater 映射到 XML 的根元素 theater. XmlElement 属性将 Name, Phone, 和 Movies 数据域映射到嵌套在 theater 元素中的 name, phone, 和 movie XML 元素上去. 因为 Movies 是 Movie 数组, 所以 XmlSerializer 将它映射到多个 XML movie 元素.
表 C 展示了一个带有属性映射的 Movie 类
表 C
public class Movie
{
[XmlElement( "title" )]
public string Title = "";
[XmlAttribute( "minutes" )]
public uint Minutes = 0;
[XmlElement( "showing", DataType="time" )]
public DateTime[] Showings;
public override string ToString()
{
string showings = "";
if ( Showings != null )
{
showings = "shows at ";
foreach ( DateTime showing in Showings )
showings += showing.ToShortTimeString() + " ";
}
else
{
showings = "- No showings";
}
return String.Format( " {0} ({1} min) {2}",
Title, Minutes, showings );
}
}
XmlElement 属性将 Title 和 Showings 数据域映射到 movie 元素内的 title 和 showing XML 元素.就象 Theater.Movie 一样, 做为 DateTime 数组的 Movie.Showings 被映射到多个 XML showing 元素. showing 数据域的属性包括位置属性参数 DataType="time". 它将 DateTime 值映射到一个 XML time 值, 其间去掉了日期信息而只保留了时间信息. XmlAttribute 属性将 Minutes 数据域映射到 XML 属性而不是 XML 元素.
XML 数据中的 moviestars(影星)属性和 rating(上座率)元素没有被映射到 Movie 类中的任何东西上去. 当反串行化 XML 数据的时候, XmlSerializer 只是简单的跳过它不能映射的项目. 当串行化一个对象的时候, 你可以在公共数据域和你希望 XmlSerializer 跳过的属性里加上 XmlIgnore 属性.
XmlRoot, XmlElement, 和 XmlAttribute 的属性类都应包括后缀"Attribute." 在我的属性申明里, 我使用了没有后缀的缩写形式. Theater 和 Movie 类中的公共属性可以被改写成公共属性以求得更好的封装性. XmlSerializer 可以用相同的方式使用它们. 我在这里将它们做为数据域使用是为了使代码更紧凑.
将 XML 数据反串行化成对象
将 XML 数据加载到一个 Theater 对象里现在已经变得非常容易. 表 D 中的程序, XmlIn, 通过反串行化 movie showings XML 数据创建一个 Theater 对象. 这个程序通过命令行执行, 你需要指明一个输入的 XML 文件.
表 D
using System;
using System.Xml.Serialization;
using System.IO;
using Articles.TechRepublic.XmlSerialization;
public class XmlIn
{
public static void Main( string[] args )
{
if ( args.Length != 1 )
{
Console.WriteLine( "Usage: XmlIn infile.xml" );
return;
}
try
{
// Deserialize the specified file to a Theater object.
XmlSerializer xs = new XmlSerializer( typeof ( Theater ) );
FileStream fs = new FileStream( args[0], FileMode.Open );
Theater theater = (Theater)xs.Deserialize( fs );
// Display the theater object.
Console.WriteLine ( theater );
}
catch ( Exception x )
{
Console.WriteLine( "Exception: " + x.Message );
}
}
}
Output:
>XmlIn theaterIn.xml
The Camelot
(888)665-2222
The Score (120 min) shows at 4:15 PM 7:05 PM 9:40 PM
Shrek (100 min) shows at 4:00 PM 7:00 PM 9:40 PM
主要的程序代码都放在 Main 函数的 try 代码段里. 首先创建一个 XmlSerializer 对象并指明一个 System.Type 对象来告诉反串行化程序要创建的对象的类型. typeof 操作符为 Theater 类返回一个 System.Type 对象. 然后, 打开一个文件流读取输入的 XML 文件. 调用 XmlSerializer 的 Deserialize 方法, 并把文件流传递给它. Deserialize 返回对 Theater 对象的引用. Theater 和 Movie 对象中的 ToString 方法能够让你简单的输出它们.
将对象串行化到 XML 里
从一个 Theater 对象生成 XML 数据同样是容易的. 表 E 中的程序,XmlOut, 就是将一个 Theater 对象串行化到 XML 文件里. 这个程序通过命令行执行, 你需要指明输出的 XML 文件.
表 E
using System;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using Articles.TechRepublic.XmlSerialization;
public class XmlOut
{
// Returns a populated Theater object.
public static Theater GetTheater()
{
Movie movie = new Movie();
movie.Title = "O Brother, Where Art Thou?";
movie.Minutes = 102;
movie.Showings = new DateTime[3];
movie.Showings[0] = new DateTime( 2001, 8, 2, 13, 15, 0 );
movie.Showings[1] = new DateTime( 2001, 8, 2, 16, 30, 0 );
movie.Showings[2] = new DateTime( 2001, 8, 2, 19, 55, 0 );
Theater theater = new Theater();
theater.Name = "Hollywood Movies 10";
theater.Phone = "(972)555-154";
theater.Movies = new Movie[1];
theater.Movies[0] = movie;
return theater;
}
public static void Main( string[] args )
{
if ( args.Length != 1 )
{
Console.WriteLine( "Usage: XmlOut outfile.xml" );
return;
}
try
{
Theater theater = GetTheater();
// Serialize the Theater object to an XML file.
XmlSerializer xs = new XmlSerializer( typeof ( Theater ) );
FileStream fs = new FileStream( args[0], FileMode.Create );
xs.Serialize( fs, theater );
}
catch ( Exception x )
{
Console.WriteLine( "Exception: " + x.Message );
}
}
}
Invocation:
>XmlOut theaterOut.xml
theaterOut.xml contents:
<?xml version="1.0"?>
<theater
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<name>Hollywood Movies 10</name>
<phone>(972)555-154</phone>
<movie minutes="102">
<title>O Brother, Where Art Thou?</title>
<showing>13:15:00.0000000-06:00</showing>
<showing>16:30:00.0000000-06:00</showing>
<showing>19:55:00.0000000-06:00</showing>
</movie>
</theater>
主要的程序代码都放在 Main 函数的 try 代码段里. 首先通过 GetTheater 帮助函数创建一个 Theater 对象. 然后, 打开一个文件流来生成输出的 XML 文件. 调用 XmlSerializer 的 Serialize 方法, 传递给它文件流和 Theater 对象. 就是这样简单--XML 文件生成了!
输出的 theater 元素包含了为模板和模板实例命名空间生成的 XML 命名空间属性(xmlns), 虽然在这两个命名空间里这些数据并不代表任何东西. showing 元素中的-06:00 指的是美国中部时间, 或者说 GMT 时间再减去个小时, 也就是我所在的时区.
移动数据是小菜一碟
XmlSerializer 使得在对象和 XML 间移动数据变得非常容易, 只要在类里加上 XML 映射属性. 但是对于更复杂的对象模型, 手工的创建 XML 映射会变得非常的麻烦而且容易出错. 在我的下一篇文章里, 我将告诉你如何自动化这个工作并实现对你的 XML 数据的更严格的控制.