详解F#对象序列化为XML的实现方法

开发 后端
在这里我们将讨论的是F#对象序列化为XML的实现方法,希望对大家用好F#开发有所帮助。

本文将从F#对象开始,详细描述F#对象序列化为XML的实现方法,期间还与C#进行了对比。希望通过本文,能让大家更好的理解F#。

#T#

这两天在用F#写一小段代码,需要把一些对象存到外部文件中去。这个功能很容易,因为.NET本身就内置了序列化功能。方便起见,我打算将这个F#对象序列化成XML而不是二进制数据流。这意味着我需要使用XmlSerializer而不是BinaryFormatter。这本应没有问题,但是在使用时候还是发生了一些小插曲。

定义类型

在F#中有多种定义方式。除了F#特有的Record类型外,在F#中也可以定义普通的“类”,如:

 

  1. #light  
  2. module XmlSerialization  
  3. type Post() =  
  4.     [<DefaultValue>]  
  5.     val mutable Title : string 
  6.     [<DefaultValue>]  
  7.     val mutable Content : string 
  8.     [<DefaultValue>] 

val mutable Tags : string array上面的代码在XmlSerialization模块中定义了一个Post类,其中包含三个公开字段。简单地说,它和C#中的如下定义等价:

 

  1. public class Post  
  2. {  
  3.     public string Title;  
  4.     public string Content;  
  5.     public string[] Tags;  

可见,在定义这种简单类型时,F#并没有什么优势,反而需要更多的代码。

使用XmlSerializer进行序列化

原本我以为使用XmlSerializer来序列化一个对象非常容易,写一个简单的(泛型)函数就可以了:

  1. let byXmlSerializer (graph: 'a) =  
  2.     let serializer = new XmlSerializer(typeof<'a>)  
  3.     let writer = new StringWriter()  
  4.     serializer.Serialize(writer, graph)  
  5.     writer.ToString() 

使用起来更加不在话下:

 

  1. let post = new XmlSerialization.Post()  
  2. post.Title <- "Hello" 
  3. post.Content <- "World" 
  4. post.Tags <- [| "Hello""World" |]  
  5. 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类是这样定义的:

  1. public static class XmlSerialization  
  2. {  
  3.     public class Post { ... }  

虽然.NET中也有“模块”的概念,但是它和F#中的模块从各方面来讲几乎没有相同之处。F#的模块会被编译为静态类,自然模块中的方法或各种函数便成为静态类中的内嵌类型及方法。这本没有问题,从理论上来说XmlSerializer也不该有问题,不是吗?

可惜XmlSerializer的确有这样的问题,我认为这是个Bug——但就算这是个Bug也无法解决目前的状况。事实上,互联网上也有人提出这个问题,可惜半年来都没有人回应。

手动序列化

那么我又该怎么做呢?我想,算了,既然如此,我们进行手动序列化吧。反正就是简单的对象,写起来应该也不麻烦。例如在C#中我们便可以:

 

  1. public class Post  
  2. {  
  3.     ...  
  4.     public string ToXml()  
  5.     {  
  6.         var xml =   
  7.             new XElement("Post",  
  8.                 new XElement("Title"this.Title),  
  9.                 new XElement("Content"this.Content),  
  10.                 new XElement("Tags",  
  11.                     this.Tags.Select(t => new XElement("Tag", t))));  
  12.         return xml.ToString();  
  13.     }  

很简单,不是吗?但是用F#写同样的逻辑便有一些问题了,最终得到的结果是:

 

  1. type Post() =   
  2.     ...  
  3.  
  4.     member p.ToXml() =  
  5.         let xml = new XElement(XName.Get("Post"))  
  6.         xml.Add(new XElement(XName.Get("Title"), p.Title))  
  7.         xml.Add(new XElement(XName.Get("Content"), p.Content))  
  8.  
  9.         let tagElements = p.Tags |> Array.map (fun t -> new XElement(XName.Get("Tag"), t))  
  10.         xml.Add(new XElement(XName.Get("Tags"), tagElements))  
  11.           
  12.         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分离开来,可以独立使用。那么我们不如尝试一下吧:

  1. let serialize (graph : 'a) =   
  2.     let serializer = new DataContractSerializer(typeof<'a>)  
  3.     let textWriter = new StringWriter();  
  4.     let xmlWriter = new XmlTextWriter(textWriter);  
  5.     serializer.WriteObject(xmlWriter, graph)  
  6.     textWriter.ToString() 

果然好用,DataContractSerializer并没有出现XmlSerializer那样傻乎乎地错误。自然,与之相对的反序列化函数也很容易写:

  1. let deserialize<'a> xml =   
  2.     let serializer = new DataContractSerializer(typeof<'a>)  
  3.     let textReader = new StringReader(xml)  
  4.     let xmlReader = new XmlTextReader(textReader)  
  5.     serializer.ReadObject(xmlReader) :?> 'a 

试验一下,看看效果?

 

  1. let post = new XmlSerialization.Post()  
  2. post.Title <- "Hello" 
  3. post.Content <- "World" 
  4. post.Tags <- [| "Hello""World" |]  
  5.  
  6. let xml = XmlSerialization.serialize post  
  7. 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

责任编辑:彭凡 来源: 博客园
相关推荐

2009-11-16 09:05:46

CodeTimer

2009-02-24 10:09:02

XMLJava对象

2009-09-09 15:47:27

XML序列化和反序列化

2012-04-13 10:45:59

XML

2009-09-09 14:45:41

XML序列化和反序列化

2010-04-07 16:51:59

F#

2011-06-01 15:05:02

序列化反序列化

2021-11-18 11:48:46

ObjectInputJava

2010-01-26 08:25:06

F#语法F#教程

2009-08-19 09:42:34

F#并行排序算法

2018-03-19 10:20:23

Java序列化反序列化

2011-05-18 15:20:13

XML

2009-06-14 22:01:27

Java对象序列化反序列化

2009-09-09 17:14:24

XML序列化

2011-06-01 14:26:11

序列化

2009-08-06 11:16:25

C#序列化和反序列化

2010-01-08 13:25:07

ibmdwXML

2010-01-07 10:04:18

F#函数式编程

2010-03-16 09:09:04

F#

2010-01-15 08:33:13

F#F#类型推断F#教程
点赞
收藏

51CTO技术栈公众号