本文将从F#对象开始,详细描述F#对象序列化为XML的实现方法,期间还与C#进行了对比。希望通过本文,能让大家更好的理解F#。
#T#
这两天在用F#写一小段代码,需要把一些对象存到外部文件中去。这个功能很容易,因为.NET本身就内置了序列化功能。方便起见,我打算将这个F#对象序列化成XML而不是二进制数据流。这意味着我需要使用XmlSerializer而不是BinaryFormatter。这本应没有问题,但是在使用时候还是发生了一些小插曲。
定义类型
在F#中有多种定义方式。除了F#特有的Record类型外,在F#中也可以定义普通的“类”,如:
- #light
- module XmlSerialization
- type Post() =
- [<DefaultValue>]
- val mutable Title : string
- [<DefaultValue>]
- val mutable Content : string
- [<DefaultValue>]
val mutable Tags : string array上面的代码在XmlSerialization模块中定义了一个Post类,其中包含三个公开字段。简单地说,它和C#中的如下定义等价:
- public class Post
- {
- public string Title;
- public string Content;
- public string[] Tags;
- }
可见,在定义这种简单类型时,F#并没有什么优势,反而需要更多的代码。
使用XmlSerializer进行序列化
原本我以为使用XmlSerializer来序列化一个对象非常容易,写一个简单的(泛型)函数就可以了:
- let byXmlSerializer (graph: 'a) =
- let serializer = new XmlSerializer(typeof<'a>)
- let writer = new StringWriter()
- serializer.Serialize(writer, graph)
- writer.ToString()
使用起来更加不在话下:
- let post = new XmlSerialization.Post()
- post.Title <- "Hello"
- post.Content <- "World"
- post.Tags <- [| "Hello"; "World" |]
- let xml = XmlSerialization.byXmlSerializer(post)
但是,在运行的时候,XmlSerializer的构造函数却抛出了InvalidOperationException:
XmlSerialization cannot be serialized. Static types cannot be used as parameters or return types.
这句话的提示似乎是在说XmlSerialization是一个静态类型——但这其实是F#的模块啊。不过使用.NET Reflector查看编译后的程序集便会发现,其实Post类是这样定义的:
- public static class XmlSerialization
- {
- public class Post { ... }
- }
虽然.NET中也有“模块”的概念,但是它和F#中的模块从各方面来讲几乎没有相同之处。F#的模块会被编译为静态类,自然模块中的方法或各种函数便成为静态类中的内嵌类型及方法。这本没有问题,从理论上来说XmlSerializer也不该有问题,不是吗?
可惜XmlSerializer的确有这样的问题,我认为这是个Bug——但就算这是个Bug也无法解决目前的状况。事实上,互联网上也有人提出这个问题,可惜半年来都没有人回应。
手动序列化
那么我又该怎么做呢?我想,算了,既然如此,我们进行手动序列化吧。反正就是简单的对象,写起来应该也不麻烦。例如在C#中我们便可以:
- public class Post
- {
- ...
- public string ToXml()
- {
- var xml =
- new XElement("Post",
- new XElement("Title", this.Title),
- new XElement("Content", this.Content),
- new XElement("Tags",
- this.Tags.Select(t => new XElement("Tag", t))));
- return xml.ToString();
- }
- }
很简单,不是吗?但是用F#写同样的逻辑便有一些问题了,最终得到的结果是:
- type Post() =
- ...
- member p.ToXml() =
- let xml = new XElement(XName.Get("Post"))
- xml.Add(new XElement(XName.Get("Title"), p.Title))
- xml.Add(new XElement(XName.Get("Content"), p.Content))
- let tagElements = p.Tags |> Array.map (fun t -> new XElement(XName.Get("Tag"), t))
- xml.Add(new XElement(XName.Get("Tags"), tagElements))
- xml.ToString()
C#之所以可以写的简单,其中有诸多因素:
XElement的构造函数***使用了params object[],这意味着我们可以把参数“罗列”出来,而不需要显式地构造一个数组。
XElement的构造函数接受的其实是XName类型参数,但字符串可以被隐式地转化为XName类型。
XElement的构造函数可以将IEnumerable<XElement>对象转化为独立的元素。 但是,除了***一条外,其他两个特性在F#里都无法享受到。因此,我们只能用命令式编程的方式编写此类代码。您可以发现,这样的F#代码几乎可以被自动转化为Java代码。F#在写这样的代码时实在没有优势。
使用DataContractSerializer
手动进行XML序列化虽然并不困难,但是实在麻烦。这不是一种通用的做法,我们必须为每个类型各写一套序列化(和反序列化)逻辑,在类型字段有所改变的时候,序列化和反序列化的逻辑还必须有所变化。就在我打算写一个简单的,通用的XML序列化方法时,我忽然想到以前看到过的一篇文章,说是在.NET 3.0中发布了新的类库:DataContractSerializer。
DataContractSerializer看似和WCF有关,如DataContractAttribute,DataMemberAttribute等标记最典型的作用也一直用在WCF里。但事实上,这些类型都是定义在System.Runtime.Serialization.dll中的,这意味着这些功能从设计之初与WCF分离开来,可以独立使用。那么我们不如尝试一下吧:
- let serialize (graph : 'a) =
- let serializer = new DataContractSerializer(typeof<'a>)
- let textWriter = new StringWriter();
- let xmlWriter = new XmlTextWriter(textWriter);
- serializer.WriteObject(xmlWriter, graph)
- textWriter.ToString()
果然好用,DataContractSerializer并没有出现XmlSerializer那样傻乎乎地错误。自然,与之相对的反序列化函数也很容易写:
- let deserialize<'a> xml =
- let serializer = new DataContractSerializer(typeof<'a>)
- let textReader = new StringReader(xml)
- let xmlReader = new XmlTextReader(textReader)
- serializer.ReadObject(xmlReader) :?> 'a
试验一下,看看效果?
- let post = new XmlSerialization.Post()
- post.Title <- "Hello"
- post.Content <- "World"
- post.Tags <- [| "Hello"; "World" |]
- let xml = XmlSerialization.serialize post
- let post' = XmlSerialization.deserialize<XmlSerialization.Post> xml
经过更多试验,我发现DataContractSerializer对于复杂类型的字段也可以正常应对,而得到这些功能也只需要在目标类型上标记一个SerializableAttribute就行了,更细节的控制也可以通过DataContractAttribute等进行控制。这样看来,XmlSerializer似乎已经可以退出历史舞台了?
原文标题:F#中的XML序列化
链接: http://www.cnblogs.com/JeffreyZhao/archive/2010/01/03/fsharp-xml-serialization.html