XML是一种很常见的数据保存方式,我经常用它来保存一些数据,或者是一些配置参数。 使用C#,我们可以借助.net framework提供的很多API来读取或者创建修改这些XML, 然而,不同人使用XML的方法很有可能并不相同。 今天我打算谈谈我使用XML的一些方法,供大家参考。

最简单的使用XML的方法

由于.net framework针对XML提供了很多API,这些API根据不同的使用场景实现了不同层次的封装, 比如,我们可以直接使用XmlTextReader、XmlDocument、XPath来取数XML中的数据, 也可以使用LINQ TO XML或者反序列化的方法从XML中读取数据。 那么,使用哪种方法最简单呢?

我个人倾向于使用序列化,反序列化的方法来使用XML。 采用这种方法,我只要考虑如何定义数据类型就可以了,读写XML各只需要一行调用即可完成。 例如:

// 1. 首先要创建或者得到一个数据对象
Order order = GetOrderById(123); // 2. 用序列化的方法生成XML
string xml = XmlHelper.XmlSerialize(order, Encoding.UTF8); // 3. 从XML读取数据并生成对象
Order order2 = XmlHelper.XmlDeserialize<Order>(xml, Encoding.UTF8);

就是这么简单的事情,XML结构是什么样的,我根本不用关心, 我只关心数据是否能保存以及下次是否能将它们读取出来。

说明:XmlHelper是一个工具类,全部源代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml; // 此处代码来源于博客【在.net中读写config文件的各种方法】的示例代码
// http://www.cnblogs.com/fish-li/archive/2011/12/18/2292037.html namespace MyMVC
{
public static class XmlHelper
{
private static void XmlSerializeInternal(Stream stream, object o, Encoding encoding)
{
if( o == null )
throw new ArgumentNullException("o");
if( encoding == null )
throw new ArgumentNullException("encoding"); XmlSerializer serializer = new XmlSerializer(o.GetType()); XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineChars = "\r\n";
settings.Encoding = encoding;
settings.IndentChars = " "; using( XmlWriter writer = XmlWriter.Create(stream, settings) ) {
serializer.Serialize(writer, o);
writer.Close();
}
} /// <summary>
/// 将一个对象序列化为XML字符串
/// </summary>
/// <param name="o">要序列化的对象</param>
/// <param name="encoding">编码方式</param>
/// <returns>序列化产生的XML字符串</returns>
public static string XmlSerialize(object o, Encoding encoding)
{
using( MemoryStream stream = new MemoryStream() ) {
XmlSerializeInternal(stream, o, encoding); stream.Position = 0;
using( StreamReader reader = new StreamReader(stream, encoding) ) {
return reader.ReadToEnd();
}
}
} /// <summary>
/// 将一个对象按XML序列化的方式写入到一个文件
/// </summary>
/// <param name="o">要序列化的对象</param>
/// <param name="path">保存文件路径</param>
/// <param name="encoding">编码方式</param>
public static void XmlSerializeToFile(object o, string path, Encoding encoding)
{
if( string.IsNullOrEmpty(path) )
throw new ArgumentNullException("path"); using( FileStream file = new FileStream(path, FileMode.Create, FileAccess.Write) ) {
XmlSerializeInternal(file, o, encoding);
}
} /// <summary>
/// 从XML字符串中反序列化对象
/// </summary>
/// <typeparam name="T">结果对象类型</typeparam>
/// <param name="s">包含对象的XML字符串</param>
/// <param name="encoding">编码方式</param>
/// <returns>反序列化得到的对象</returns>
public static T XmlDeserialize<T>(string s, Encoding encoding)
{
if( string.IsNullOrEmpty(s) )
throw new ArgumentNullException("s");
if( encoding == null )
throw new ArgumentNullException("encoding"); XmlSerializer mySerializer = new XmlSerializer(typeof(T));
using( MemoryStream ms = new MemoryStream(encoding.GetBytes(s)) ) {
using( StreamReader sr = new StreamReader(ms, encoding) ) {
return (T)mySerializer.Deserialize(sr);
}
}
} /// <summary>
/// 读入一个文件,并按XML的方式反序列化对象。
/// </summary>
/// <typeparam name="T">结果对象类型</typeparam>
/// <param name="path">文件路径</param>
/// <param name="encoding">编码方式</param>
/// <returns>反序列化得到的对象</returns>
public static T XmlDeserializeFromFile<T>(string path, Encoding encoding)
{
if( string.IsNullOrEmpty(path) )
throw new ArgumentNullException("path");
if( encoding == null )
throw new ArgumentNullException("encoding"); string xml = File.ReadAllText(path, encoding);
return XmlDeserialize<T>(xml, encoding);
}
}
}

或许有人会说:我使用XPath从XML读取数据也很简单啊。 我认为这种说法有一个限制条件:只需要从XML中读取少量的数据。 如果要全部读取,用这种方法会写出一大堆的机械代码出来! 所以,我非常反感用这种方法从XML中读取全部数据。

类型定义与XML结构的映射

如果是一个新项目,我肯定会毫不犹豫的使用序列化和反序列化的方法来使用XML, 然而,有时在维护一个老项目时,面对一堆只有XML却没有与之对应的C#类型时, 我们就需要根据XML结构来逆向推导C#类型,然后才能使用序列化和反序列化的方法。 逆向推导的过程是麻烦的,不过,类型推导出来之后,后面的事情就简单多了。

为了学会根据XML结构逆向推导类型,我们需要关注一下类型定义与XML结构的映射关系。 注意:有时候我们也会考虑XML结构对于传输量及可阅读性的影响,所以关注一下XML也是有必要的。

这里有一个XML文件,是我从Visual Sutdio的安装目录中找到的:

<DynamicHelp xmlns="http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd vsdh.xsd">
<LinkGroup ID="sites" Title="Venus Sites" Priority="1500">
<Glyph Collapsed="3" Expanded="4"/>
</LinkGroup>
<LinkGroup ID="Venus Private Forums" Title="Venus Private Forums" Priority="1400">
<Glyph Collapsed="3" Expanded="4"/>
</LinkGroup>
<LinkGroup ID="ASP.NET Forums" Title="ASP.NET 1.0 Public Forums" Priority="1200">
<Glyph Collapsed="3" Expanded="4"/>
</LinkGroup>
<Context>
<Links>
<LItem URL="http://www.asp.net/venus" LinkGroup="sites">Venus Home Page</LItem>
<LItem URL="http://www.asp.net" LinkGroup="sites">ASP.NET Home Page</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=77"
LinkGroup="Venus Private Forums">General Discussions</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=83"
LinkGroup="Venus Private Forums">Feature Requests</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=78"
LinkGroup="Venus Private Forums">Bug Reports</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=86"
LinkGroup="Venus Private Forums">ASP.NET 2.0 Related issues</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=11"
LinkGroup="ASP.NET Forums">Announcements</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=15"
LinkGroup="ASP.NET Forums">Getting Started</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=18"
LinkGroup="ASP.NET Forums">Web Forms</LItem>
</Links>
</Context>
</DynamicHelp>

怎样用反序列化的方式来读取它的数据呢,我在博客的最后将给出完整的实现代码。 现在,我们还是看一下这个XML有哪些特点吧。

<LinkGroup ID="sites" Title="Venus Sites" Priority="1500">

对于这个节点来说,它包含了三个数据项(属性):ID,Title,Priority。 这样的LinkGroup节点有三个。 类似的还有Glyph节点。

<LItem URL="http://www.asp.net" LinkGroup="sites">ASP.NET Home Page</LItem>

LItem节点除了与LinkGroup有着类似的数据(属性)之外,还包含着一个字符串:ASP.NET Home Page , 这是另外一种数据的存放方式。

另外,LinkGroup和LItem都允许重复出现,我们可以用数组或者列表(Array,List)来理解它们。

我还发现一些嵌套关系:LinkGroup可以包含Glyph,Context包含着Links,Links又包含了多个LItem。 不管如何嵌套,我发现数据都是包含在一个一个的XML节点中。

如果用专业的单词来描述它们,我们可以将ID,Title,Priority这三个数据项称为 XmlAttribute, LItem,LinkGroup节点称为 XmlElement,”ASP.NET Home Page“出现的位置可以称为 InnerText。 基本上,XML就是由这三类数据组成。

下面我来演示如何使用这三种数据项。

使用 XmlElement

首先,我来定义一个类型:

public class Class1
{
public int IntValue { get; set; } public string StrValue { get; set; }
}

下面是序列化与反序列的调用代码:

Class1 c1 = new Class1 { IntValue = 3, StrValue = "Fish Li" };
string xml = XmlHelper.XmlSerialize(c1, Encoding.UTF8);
Console.WriteLine(xml); Console.WriteLine("---------------------------------------"); Class1 c2 = XmlHelper.XmlDeserialize<Class1>(xml, Encoding.UTF8);
Console.WriteLine("IntValue: " + c2.IntValue.ToString());
Console.WriteLine("StrValue: " + c2.StrValue);

运行结果如下:

<?xml version="1.0" encoding="utf-8"?>
<Class1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<IntValue>3</IntValue>
<StrValue>Fish Li</StrValue>
</Class1>
---------------------------------------
IntValue: 3
StrValue: Fish Li

结果显示,IntValue和StrValue这二个属性生成了XmlElement。

小结:默认情况下(不加任何Attribute),类型中的属性或者字段,都会生成XmlElement。

使用 XmlAttribute

再来定义一个类型:

public class Class2
{
[XmlAttribute]
public int IntValue { get; set; } [XmlElement]
public string StrValue { get; set; }
}

注意,我在二个属性上增加的不同的Attribute.

下面是序列化与反序列的调用代码:

Class2 c1 = new Class2 { IntValue = 3, StrValue = "Fish Li" };
string xml = XmlHelper.XmlSerialize(c1, Encoding.UTF8);
Console.WriteLine(xml); Console.WriteLine("---------------------------------------"); Class2 c2 = XmlHelper.XmlDeserialize<Class2>(xml, Encoding.UTF8);
Console.WriteLine("IntValue: " + c2.IntValue.ToString());
Console.WriteLine("StrValue: " + c2.StrValue);

运行结果如下(我将结果做了换行处理):

<?xml version="1.0" encoding="utf-8"?>
<Class2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
IntValue="3">
<StrValue>Fish Li</StrValue>
</Class2>
---------------------------------------
IntValue: 3
StrValue: Fish Li

结果显示: 1. IntValue 生成了XmlAttribute 2. StrValue 生成了XmlElement(和不加[XmlElement]的效果一样,表示就是默认行为)。

小结:如果希望类型中的属性或者字段生成XmlAttribute,需要在类型的成员上用[XmlAttribute]来指出。

使用 InnerText

还是来定义一个类型:

public class Class3
{
[XmlAttribute]
public int IntValue { get; set; } [XmlText]
public string StrValue { get; set; }
}

注意,我在StrValue上增加的不同的Attribute.

下面是序列化与反序列的调用代码:

Class3 c1 = new Class3 { IntValue = 3, StrValue = "Fish Li" };
string xml = XmlHelper.XmlSerialize(c1, Encoding.UTF8);
Console.WriteLine(xml); Console.WriteLine("---------------------------------------"); Class3 c2 = XmlHelper.XmlDeserialize<Class3>(xml, Encoding.UTF8);
Console.WriteLine("IntValue: " + c2.IntValue.ToString());
Console.WriteLine("StrValue: " + c2.StrValue);

运行结果如下(我将结果做了换行处理):

<?xml version="1.0" encoding="utf-8"?>
<Class3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
IntValue="3">Fish Li</Class3>
---------------------------------------
IntValue: 3
StrValue: Fish Li

结果符合预期:StrValue属性在增加了[XmlText]之后,生成了一个文本节点(InnerText)

小结:如果希望类型中的属性或者字段生成InnerText,需要在类型的成员上用[XmlText]来指出。

重命名节点名称

看过前面几个示例,大家应该能发现:通过序列化得到的XmlElement和XmlAttribute都与类型的数据成员或者类型同名。 然而有时候我们可以希望让属性名与XML的节点名称不一样,那么就要使用【重命名】的功能了,请看以下示例:

[XmlType("c4")]
public class Class4
{
[XmlAttribute("id")]
public int IntValue { get; set; } [XmlElement("name")]
public string StrValue { get; set; }
}

序列化与反序列的调用代码前面已经多次看到,这里就省略它们了。 运行结果如下(我将结果做了换行处理):

<?xml version="1.0" encoding="utf-8"?>
<c4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
id="3">
<name>Fish Li</name>
</c4>
---------------------------------------
IntValue: 3
StrValue: Fish Li

看看输出结果中的红字粗体字,再看看类型定义中的三个Attribute的三个字符串参数,我想你能发现规律的。

小结:XmlAttribute,XmlElement允许接受一个别名用来控制生成节点的名称,类型的重命名用XmlType来实现。

列表和数组的序列化

继续看示例代码:

Class4 c1 = new Class4 { IntValue = 3, StrValue = "Fish Li" };
Class4 c2 = new Class4 { IntValue = 4, StrValue = "http://www.cnblogs.com/fish-li/" }; // 说明:下面二行代码的输出结果是一样的。
List<Class4> list = new List<Class4> { c1, c2 };
//Class4[] list = new Class4[] { c1, c2 }; string xml = XmlHelper.XmlSerialize(list, Encoding.UTF8);
Console.WriteLine(xml); // 序列化的结果,反序列化一定能读取,所以就不再测试反序列化了。

运行结果如下:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfC4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<c4 id="3">
<name>Fish Li</name>
</c4>
<c4 id="4">
<name>http://www.cnblogs.com/fish-li/</name>
</c4>
</ArrayOfC4>

现在c4节点已经重复出现了,显然,是我们期待的结果。

不过,ArrayOfC4,这个节点名看起来太奇怪了,能不能给它也重命名呢? 继续看代码,我可以定义一个新的类型:

// 二种Attribute都可以完成同样的功能。
//[XmlType("c4List")]
[XmlRoot("c4List")]
public class Class4List : List<Class4> { }

然后,改一下调用代码:

Class4List list = new Class4List { c1, c2 };

运行结果如下:

<?xml version="1.0" encoding="utf-8"?>
<c4List xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<c4 id="3">
<name>Fish Li</name>
</c4>
<c4 id="4">
<name>http://www.cnblogs.com/fish-li/</name>
</c4>
</c4List>

小结:数组和列表都能直接序列化,如果要重命名根节点名称,需要创建一个新类型来实现。

列表和数组的做为数据成员的序列化

首先,还是定义一个类型:

public class Root
{
public Class3 Class3 { get; set; } public List<Class2> List { get; set; }
}

序列化的调用代码:

Class2 c1 = new Class2 { IntValue = 3, StrValue = "Fish Li" };
Class2 c2 = new Class2 { IntValue = 4, StrValue = "http://www.cnblogs.com/fish-li/" }; Class3 c3 = new Class3 { IntValue = 5, StrValue = "Test List" }; Root root = new Root { Class3 = c3, List = new List<Class2> { c1, c2 } }; string xml = XmlHelper.XmlSerialize(root, Encoding.UTF8);
Console.WriteLine(xml);

运行结果如下:

<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Class3 IntValue="5">Test List</Class3>
<List>
<Class2 IntValue="3">
<StrValue>Fish Li</StrValue>
</Class2>
<Class2 IntValue="4">
<StrValue>http://www.cnblogs.com/fish-li/</StrValue>
</Class2>
</List>
</Root>

假设这里需要为List和Class2的节点重命名,该怎么办呢? 如果继续使用前面介绍的方法,是行不通的。

下面的代码演示了如何重命名列表节点的名称:

public class Root
{
public Class3 Class3 { get; set; } [XmlArrayItem("c2")]
[XmlArray("cccccccccccc")]
public List<Class2> List { get; set; }
}

序列化的调用代码与前面完全一样,得到的输出结果如下:

<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Class3 IntValue="5">Test List</Class3>
<cccccccccccc>
<c2 IntValue="3">
<StrValue>Fish Li</StrValue>
</c2>
<c2 IntValue="4">
<StrValue>http://www.cnblogs.com/fish-li/</StrValue>
</c2>
</cccccccccccc>
</Root>

想不想把cccccccccccc节点去掉呢(直接出现c2节点)? 下面的类型定义方式实现了这个想法:

public class Root
{
public Class3 Class3 { get; set; } [XmlElement("c2")]
public List<Class2> List { get; set; }
}

输出结果如下:

<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Class3 IntValue="5">Test List</Class3>
<c2 IntValue="3">
<StrValue>Fish Li</StrValue>
</c2>
<c2 IntValue="4">
<StrValue>http://www.cnblogs.com/fish-li/</StrValue>
</c2>
</Root>

小结:数组和列表都在序列化时,默认情况下会根据类型中的数据成员名称生成一个节点, 列表项会生成子节点,如果要重命名,可以使用[XmlArrayItem]和[XmlArray]来实现。 还可以直接用[XmlElement]控制不生成列表的父节点。

类型继承与反序列化

列表元素可以是同一种类型,也可以不是同一种类型(某个类型的派生类)。 例如下面的XML:

<?xml version="1.0" encoding="utf-8"?>
<XRoot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<List>
<x1 aa="1" bb="2" />
<x1 aa="3" bb="4" />
<x2>
<cc>ccccccccccc</cc>
<dd>dddddddddddd</dd>
</x2>
</List>
</XRoot>

想像一下,上面这段XML是通过什么类型得到的呢?

答案如下(注意红色粗体部分):

public class XBase { }

[XmlType("x1")]
public class X1 : XBase
{
[XmlAttribute("aa")]
public int AA { get; set; } [XmlAttribute("bb")]
public int BB { get; set; }
} [XmlType("x2")]
public class X2 : XBase
{
[XmlElement("cc")]
public string CC { get; set; } [XmlElement("dd")]
public string DD { get; set; }
} public class XRoot
{
[XmlArrayItem(typeof(X1)),
XmlArrayItem(typeof(X2))]
public List<XBase> List { get; set; }
}

序列化代码:

X1 x1a = new X1 { AA = 1, BB = 2 };
X1 x1b = new X1 { AA = 3, BB = 4 };
X2 x2 = new X2 { CC = "ccccccccccc", DD = "dddddddddddd" };
XRoot root = new XRoot { List = new List<XBase> { x1a, x1b, x2 } }; string xml = XmlHelper.XmlSerialize(root, Encoding.UTF8);
Console.WriteLine(xml);

小结:同时为列表成员指定多个[XmlArrayItem(typeof(XXX))]可实现多种派生类型混在一起输出。

反序列化的实战演练

接下来,我们将根据前面介绍的知识点,用反序列化的方法来解析本文开头处贴出的那段XML:

<DynamicHelp xmlns="http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd vsdh.xsd">
<LinkGroup ID="sites" Title="Venus Sites" Priority="1500">
<Glyph Collapsed="3" Expanded="4"/>
</LinkGroup>
<LinkGroup ID="Venus Private Forums" Title="Venus Private Forums" Priority="1400">
<Glyph Collapsed="3" Expanded="4"/>
</LinkGroup>
<LinkGroup ID="ASP.NET Forums" Title="ASP.NET 1.0 Public Forums" Priority="1200">
<Glyph Collapsed="3" Expanded="4"/>
</LinkGroup>
<Context>
<Links>
<LItem URL="http://www.asp.net/venus" LinkGroup="sites">Venus Home Page</LItem>
<LItem URL="http://www.asp.net" LinkGroup="sites">ASP.NET Home Page</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=77"
LinkGroup="Venus Private Forums">General Discussions</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=83"
LinkGroup="Venus Private Forums">Feature Requests</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=78"
LinkGroup="Venus Private Forums">Bug Reports</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=86"
LinkGroup="Venus Private Forums">ASP.NET 2.0 Related issues</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=11"
LinkGroup="ASP.NET Forums">Announcements</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=15"
LinkGroup="ASP.NET Forums">Getting Started</LItem>
<LItem URL="http://www.asp.net/Forums/ShowForum.aspx?tabindex=1&amp;ForumID=18"
LinkGroup="ASP.NET Forums">Web Forms</LItem>
</Links>
</Context>
</DynamicHelp>

那段XML的根元素是DynamicHelp,因此,我们需要定义一个类型,类名为DynamicHelp。 再观察那段XML,它应该包含一个LinkGroup列表,和一个Context属性,所以可以这样定义这三个类型:

public class DynamicHelp
{
[XmlElement]
public List<LinkGroup> Groups { get; set; } public Context Context { get; set; }
} public class LinkGroup { } public class Context { }

再来看LinkGroup,它包含三个数据成员,以及一个子节点:Glyph,因此可以这样定义它们:

public class LinkGroup
{
[XmlAttribute]
public string ID { get; set; }
[XmlAttribute]
public string Title { get; set; }
[XmlAttribute]
public int Priority { get; set; } public Glyph Glyph { get; set; }
} public class Glyph
{
[XmlAttribute]
public int Collapsed { get; set; }
[XmlAttribute]
public int Expanded { get; set; }
}

LItem节点也简单,它就包含了URL,LinkGroup和一个文本节点,因此可以这样定义它:

public class LItem
{
[XmlAttribute]
public string URL { get; set; }
[XmlAttribute]
public string LinkGroup { get; set; } [XmlText]
public string Title { get; set; }
}

Context节点也不复杂,就只包含了一个LItem列表,因此可以这样定义它:

public class Context
{
public List<LItem> Links { get; set; }
}

好了,类型都定义好了,再来试试反序列化:

DynamicHelp help = XmlHelper.XmlDeserializeFromFile<DynamicHelp>("Links.xml", Encoding.UTF8);

foreach( LinkGroup group in help.Groups )
Console.WriteLine("ID: {0}, Title: {1}, Priority: {2}, Collapsed: {3}, Expanded: {4}",
group.ID, group.Title, group.Priority, group.Glyph.Collapsed, group.Glyph.Expanded); foreach( LItem item in help.Context.Links )
Console.WriteLine("URL: {0}, LinkGroup: {1}, Title: {2}",
item.URL.Substring(0, 15), item.LinkGroup, item.Title);

屏幕显示:

未处理的异常:  System.InvalidOperationException: XML 文档(4, 2)中有错误。
---> System.InvalidOperationException: 不应有
<DynamicHelp xmlns='http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd'>。

哦,抛异常了。 别急,看看异常说什么。 好像是在说命名空间不能识别。 根据异常的描述,我还要修改一下DynamicHelp的定义,改成这样:

[XmlRoot(Namespace = "http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd")]
public class DynamicHelp

再次运行,结果如下:

ID: sites, Title: Venus Sites, Priority: 1500, Collapsed: 3, Expanded: 4
ID: Venus Private Forums, Title: Venus Private Forums, Priority: 1400, Collapsed: 3, Expanded: 4
ID: ASP.NET Forums, Title: ASP.NET 1.0 Public Forums, Priority: 1200, Collapsed: 3, Expanded: 4
URL: http://www.asp., LinkGroup: sites, Title: Venus Home Page
URL: http://www.asp., LinkGroup: sites, Title: ASP.NET Home Page
URL: http://www.asp., LinkGroup: Venus Private Forums, Title: General Discussions
URL: http://www.asp., LinkGroup: Venus Private Forums, Title: Feature Requests
URL: http://www.asp., LinkGroup: Venus Private Forums, Title: Bug Reports
URL: http://www.asp., LinkGroup: Venus Private Forums, Title: ASP.NET 2.0 Related issues
URL: http://www.asp., LinkGroup: ASP.NET Forums, Title: Announcements
URL: http://www.asp., LinkGroup: ASP.NET Forums, Title: Getting Started
URL: http://www.asp., LinkGroup: ASP.NET Forums, Title: Web Forms

小结:根据XML结构推导类型时,要保证类型的层次结构与XML匹配, 数据的存放方式可以通过[XmlElement],[XmlAttribute],[XmlText]方式来指出。

反序列化的使用总结

如果XML是由类型序列化得到那的,那么反序列化的调用代码是很简单的, 反之,如果要面对一个没有类型的XML,就需要我们先设计一个(或者一些)类型出来, 这是一个逆向推导的过程,请参考以下步骤: 1. 首先要分析整个XML结构,定义与之匹配的类型, 2. 如果XML结构有嵌套层次,则需要定义多个类型与之匹配, 3. 定义具体类型(一个层级下的XML结构)时,请参考以下表格。

XML形式 处理方法 补充说明
XmlElement 定义一个属性 属性名与节点名字匹配
XmlAttribute [XmlAttribute] 加到属性上  
InnerText [XmlText] 加到属性上 一个类型只能使用一次
节点重命名 根节点:[XmlType("testClass")] 元素节点:[XmlElement("name")] 属性节点:[XmlAttribute("id")] 列表子元素节点:[XmlArrayItem("Detail")] 列表元素自身:[XmlArray("Items")]  

排除不需要序列化的成员

默认情况下,类型的所有公开的数据成员(属性,字段)在序列化时都会被输出, 如果希望排除某些成员,可以用[XmlIgnore]来指出,例如:

public class TestIgnore
{
[XmlIgnore] // 这个属性将不会参与序列化
public int IntValue { get; set; } public string StrValue { get; set; } public string Url;
}

序列化调用代码:

TestIgnore c1 = new TestIgnore { IntValue = 3, StrValue = "Fish Li" };
c1.Url = "http://www.cnblogs.com/fish-li/"; string xml = XmlHelper.XmlSerialize(c1, Encoding.UTF8);
Console.WriteLine(xml);

输出结果如下:

<?xml version="1.0" encoding="utf-8"?>
<TestIgnore xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Url>http://www.cnblogs.com/fish-li/</Url>
<StrValue>Fish Li</StrValue>
</TestIgnore>

强制指定成员的序列化顺序

前面的示例很奇怪,我明明先定义的StrValue,后定义的Url,可是在输出时的顺序并是我期望的。 如果你希望控制序列化的输出顺序,可以参考下面的示例代码(注意红色粗体文字):

public class TestIgnore
{
[XmlIgnore] // 这个属性将不会参与序列化
public int IntValue { get; set; } [XmlElement(Order = 1)]
public string StrValue { get; set; } [XmlElement(Order = 2)]
public string Url;
}

最终的输出结果如下:

<?xml version="1.0" encoding="utf-8"?>
<TestIgnore xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<StrValue>Fish Li</StrValue>
<Url>http://www.cnblogs.com/fish-li/</Url>
</TestIgnore>

自定义序列化行为

由于种种原因,可能需要我们自己控制序列化和反序列化的过程, 对于这种需求, .net framework也是支持的,下面我来演示如何这个过程。

假如我现在有这样的类型定义:

public class TestClass
{
public string StrValue { get; set; } public List<int> List { get; set; }
} public class ClassB1
{
public TestClass Test { get; set; }
}

测试代码:

TestClass test = new TestClass { StrValue = "Fish Li", List = new List<int> { 1, 2, 3, 4, 5 } };
ClassB1 b1 = new ClassB1 { Test = test }; string xml = XmlHelper.XmlSerialize(b1, Encoding.UTF8);
Console.WriteLine(xml); Console.WriteLine("-----------------------------------------------------"); ClassB1 b2 = XmlHelper.XmlDeserialize<ClassB1>(xml, Encoding.UTF8);
Console.WriteLine("StrValue: " + b2.Test.StrValue);
foreach( int n in b2.Test.List )
Console.WriteLine(n);

此时程序的输出结果如下:

<?xml version="1.0" encoding="utf-8"?>
<ClassB1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Test>
<StrValue>Fish Li</StrValue>
<List>
<int>1</int>
<int>2</int>
<int>3</int>
<int>4</int>
<int>5</int>
</List>
</Test>
</ClassB1>
-----------------------------------------------------
StrValue: Fish Li
1
2
3
4
5

现在我可能会想:TestClass这个类太简单了,但它输出的XML长度复杂了点,能不能再短小一点,让网络传输地更快呢?

在这里,我想到了自定义序列化行为来实现,请看下面对TestClass的重新定义。

public class TestClass : IXmlSerializable
{
public string StrValue { get; set; } public List<int> List { get; set; } public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
} public void ReadXml(XmlReader reader)
{
StrValue = reader.GetAttribute("s"); string numbers = reader.ReadString();
if( string.IsNullOrEmpty(numbers) == false )
List = (from s in numbers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
let n = int.Parse(s)
select n).ToList();
} public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("s", StrValue);
writer.WriteString(string.Join(",", List.ConvertAll<string>(x => x.ToString()).ToArray()));
}
}

继续使用前面的测试代码,现在的输出结果如下:

<?xml version="1.0" encoding="utf-8"?>
<ClassB1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Test s="Fish Li">1,2,3,4,5</Test>
</ClassB1>
-----------------------------------------------------
StrValue: Fish Li
1
2
3
4
5

很明显,现在的序列化结果要比以前的结果小很多。 而且,测试代码中的反序列化的显示也表明,我们仍然可以通过反序列化来读取它。

序列化去掉XML命名空间及声明头

在前面的示例中,我们会发现有时很简单的XML在加了命名空间及声明头以后,结构变复杂了,内容也变长了。 有些人看到它们可能总是感觉非常别扭,例如:

<?xml version="1.0" encoding="utf-8"?>
<ClassB1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Test s="Fish Li">1,2,3,4,5</Test>
</ClassB1>

能不能只显示成下面这样呢?

<ClassB1>
<Test s="Fish Li">1,2,3,4,5</Test>
</ClassB1>

答案是肯定的,按下面的方法修改本文的示例代码:

private static void XmlSerializeInternal(Stream stream, object o, Encoding encoding)
{
if( o == null )
throw new ArgumentNullException("o");
if( encoding == null )
throw new ArgumentNullException("encoding"); XmlSerializer serializer = new XmlSerializer(o.GetType()); XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineChars = "\r\n";
settings.Encoding = encoding;
settings.IndentChars = " "; // 不生成声明头
settings.OmitXmlDeclaration = true; // 强制指定命名空间,覆盖默认的命名空间。
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty); using( XmlWriter writer = XmlWriter.Create(stream, settings) ) {
serializer.Serialize(writer, o, namespaces);
writer.Close();
}
}

说明:去掉XML命名空间及声明头不影响反序列化。

XML的使用建议

在服务端,C#代码中: 1. 建议不用使用低级别的XML API来使用XML,除非你是在设计框架或者通用类库。 2. 建议使用序列化、反序列化的方法来生成或者读取XML 3. 当需要考虑使用XML时,先不要想着XML结构,先应该定义好数据类型。 4. 列表节点不要使用[XmlElement],它会让所有子节点【升级】,显得结构混乱。 5. 如果希望序列化的XML长度小一点,可以采用[XmlAttribute],或者指定一个更短小的别名。 6. 不要在一个列表中输出不同的数据类型,这样的XML结构的可读性不好。 7. 尽量使用UTF-8编码,不要使用GB2312编码。

在客户端,JavaScript代码中,我不建议使用XML,而是建议使用JSON来代替XML,因为: 1. XML文本的长度比JSON要长,会占用更多的网络传输时间(毕竟数据保存在服务端,所以传输是免不了的) 2. 在JavaScritp中使用XML比较麻烦(还有浏览器的兼容问题),反而各种浏览器对JSON有非常好的支持。

点击此处下载示例代码

C#_在.net中序列化读写xml方法的总结的更多相关文章

  1. 在.net中序列化读写xml方法的总结

    在.net中序列化读写xml方法的总结 阅读目录 开始 最简单的使用XML的方法 类型定义与XML结构的映射 使用 XmlElement 使用 XmlAttribute 使用 InnerText 重命 ...

  2. (转)在.net中序列化读写xml方法的总结

    阅读目录 开始 最简单的使用XML的方法 类型定义与XML结构的映射 使用 XmlElement 使用 XmlAttribute 使用 InnerText 重命名节点名称 列表和数组的序列化 列表和数 ...

  3. 在.net中序列化读写xml方法的总结--转载过来学习学习

    原文章地址:http://www.cnblogs.com/fish-li/archive/2013/05/05/3061816.html 首先做个大概的总结,XML包括的元素有XmlElement,X ...

  4. 在.net中序列化读写xml方法

    收集XML的写法 XML是一种很常见的数据保存方式,我经常用它来保存一些数据,或者是一些配置参数. 使用C#,我们可以借助.net framework提供的很多API来读取或者创建修改这些XML, 然 ...

  5. net中序列化读写xml

    参考http://www.cnblogs.com/fish-li/archive/2013/05/05/3061816.html 我们可以直接使用XmlTextReader.XmlDocument.X ...

  6. C#中XmlTextWriter读写xml文件详细介绍

    XmlTextWriter类允许你将XML写到一个文件中去.这个类包含了很多方法和属性,使用这些属性和方法可以使你更容易地处理XML.为了使用这个类,你必须首先创建一个新的XmlTextWriter对 ...

  7. C#中XmlTextWriter读写xml文件详细介绍(转)

    转自http://www.jb51.net/article/35230.htm   .NET中包含了很多支持XML的类,这些类使得程序员使用XML编程就如同理解XML文件一样简单.在这篇文章中,我将给 ...

  8. UE4使用第三方库读写xml文件

    原文链接:http://gad.qq.com/article/detail/7181031 本文首发腾讯GAD开发者平台,未经允许,不得转载 在游戏开发过程中,读写xml几乎已经成为不可或缺的功能,但 ...

  9. VC 使用msxml6.dll动态链接库中的函数读写XML文件

    VC 使用msxml6.dll动态链接库中的函数读写XML文件 目录 1 引言 2 .dll使用方法 3 常用函数总结 4 实例应用 5 运行效果预览 6 补充说明 7 不足之处 8 更新   引言: ...

随机推荐

  1. VC++6.0 MFC播放视频

    注:需要在windows xp下才可以使用Windows Media Player插件,在windows 7下面会找不到该插件. 1.Windows Media Player控件的主要方法: 1)Ge ...

  2. C#中常用的字符串加密,解密方法封装,包含只加密,不解密的方法

    //方法一//须添加对System.Web的引用//using System.Web.Security;/// <summary>/// SHA1加密字符串/// </summary ...

  3. net中前台javascript与后台c#函数相互调用

    问: 1.如何在JavaScript访问C#函数? 2.如何在JavaScript访问C#变量? 3.如何在C#中访问JavaScript的已有变量? 4.如何在C#中访问JavaScript函数? ...

  4. 限制波尔兹曼机(Restricted Boltzmann Machines)

    能量模型的概念从统计力学中得来,它描述着整个系统的某种状态,系统越有序,系统能量波动越小,趋近于平衡状态,系统越无序,能量波动越大.例如:一个孤立的物体,其内部各处的温度不尽相同,那么热就从温度较高的 ...

  5. 开源框架DNN使用01

    我先简单地介绍下我个人对于DNN的浅显理解吧. 我觉得对于刚接触的人来说首先理解DNN的原理,大框架是很重要的.它整个网站其实是没几个页面的,从源码上就可以看出, 一个Default页.一个Error ...

  6. hdfs[命令] fsck

    Usage: DFSck <path> [-list-corruptfileblocks | [-move | -delete | -openforwrite] [-files [-blo ...

  7. av_interleaved_write_frame 网络不好的情况下返回较慢

    用libvlc做直播推流引擎在网络较差的情况下,需要关闭直播,并且重新开播.这个过程中,推流引擎重启,需要的是快速响应.实际上测试结果发现,经常会发生引擎关闭接口卡住.后来跟踪代码,定位到s_rtmp ...

  8. Glibc辅助运行库 (C RunTime Library): crt0.o,crt1.o,crti.o crtn.o,crtbegin.o crtend.o

    crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o 等目标文件和daemon.o(由我们自己的C程序文件产生)链接成一个执行文件.前面这5个目标文件的作用分别是启 ...

  9. (转)php的扩展和嵌入--php的生命周期与变量详述

    本文转自http://blog.csdn.net/cedricliang/article/details/17247749?9435:这是在我想在js的循环中加入一段php,这段php代码会在每次执行 ...

  10. Java设计模式系列之单例模式

    单例模式的定义 一个类有且仅有一个实例,并且自行实例化向整个系统提供.比如,多程序读取一个配置文件时,建议配置文件时,建议配置文件封装成对象.会方便操作其中的数据,又要保证多个程序读到的是同一个配置文 ...