新聞中心
本文將從F#對象開始,詳細(xì)描述F#對象序列化為XML的實(shí)現(xiàn)方法,期間還與C#進(jìn)行了對比。希望通過本文,能讓大家更好的理解F#。

#T#
這兩天在用F#寫一小段代碼,需要把一些對象存到外部文件中去。這個(gè)功能很容易,因?yàn)?NET本身就內(nèi)置了序列化功能。方便起見,我打算將這個(gè)F#對象序列化成XML而不是二進(jìn)制數(shù)據(jù)流。這意味著我需要使用XmlSerializer而不是BinaryFormatter。這本應(yīng)沒有問題,但是在使用時(shí)候還是發(fā)生了一些小插曲。
定義類型
在F#中有多種定義方式。除了F#特有的Record類型外,在F#中也可以定義普通的“類”,如:
- #light
- module XmlSerialization
- type Post() =
- [
] - val mutable Title : string
- [
] - val mutable Content : string
- [
]
val mutable Tags : string array上面的代碼在XmlSerialization模塊中定義了一個(gè)Post類,其中包含三個(gè)公開字段。簡單地說,它和C#中的如下定義等價(jià):
- public class Post
- {
- public string Title;
- public string Content;
- public string[] Tags;
- }
可見,在定義這種簡單類型時(shí),F(xiàn)#并沒有什么優(yōu)勢,反而需要更多的代碼。
使用XmlSerializer進(jìn)行序列化
原本我以為使用XmlSerializer來序列化一個(gè)對象非常容易,寫一個(gè)簡單的(泛型)函數(shù)就可以了:
- 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)
但是,在運(yùn)行的時(shí)候,XmlSerializer的構(gòu)造函數(shù)卻拋出了InvalidOperationException:
XmlSerialization cannot be serialized. Static types cannot be used as parameters or return types.
這句話的提示似乎是在說XmlSerialization是一個(gè)靜態(tài)類型——但這其實(shí)是F#的模塊啊。不過使用.NET Reflector查看編譯后的程序集便會(huì)發(fā)現(xiàn),其實(shí)Post類是這樣定義的:
- public static class XmlSerialization
- {
- public class Post { ... }
- }
雖然.NET中也有“模塊”的概念,但是它和F#中的模塊從各方面來講幾乎沒有相同之處。F#的模塊會(huì)被編譯為靜態(tài)類,自然模塊中的方法或各種函數(shù)便成為靜態(tài)類中的內(nèi)嵌類型及方法。這本沒有問題,從理論上來說XmlSerializer也不該有問題,不是嗎?
可惜XmlSerializer的確有這樣的問題,我認(rèn)為這是個(gè)Bug——但就算這是個(gè)Bug也無法解決目前的狀況。事實(shí)上,互聯(lián)網(wǎng)上也有人提出這個(gè)問題,可惜半年來都沒有人回應(yīng)。
手動(dòng)序列化
那么我又該怎么做呢?我想,算了,既然如此,我們進(jìn)行手動(dòng)序列化吧。反正就是簡單的對象,寫起來應(yīng)該也不麻煩。例如在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#寫同樣的邏輯便有一些問題了,最終得到的結(jié)果是:
- 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的構(gòu)造函數(shù)***使用了params object[],這意味著我們可以把參數(shù)“羅列”出來,而不需要顯式地構(gòu)造一個(gè)數(shù)組。
XElement的構(gòu)造函數(shù)接受的其實(shí)是XName類型參數(shù),但字符串可以被隱式地轉(zhuǎn)化為XName類型。
XElement的構(gòu)造函數(shù)可以將IEnumerable
使用DataContractSerializer
手動(dòng)進(jìn)行XML序列化雖然并不困難,但是實(shí)在麻煩。這不是一種通用的做法,我們必須為每個(gè)類型各寫一套序列化(和反序列化)邏輯,在類型字段有所改變的時(shí)候,序列化和反序列化的邏輯還必須有所變化。就在我打算寫一個(gè)簡單的,通用的XML序列化方法時(shí),我忽然想到以前看到過的一篇文章,說是在.NET 3.0中發(fā)布了新的類庫:DataContractSerializer。
DataContractSerializer看似和WCF有關(guān),如DataContractAttribute,DataMemberAttribute等標(biāo)記最典型的作用也一直用在WCF里。但事實(shí)上,這些類型都是定義在System.Runtime.Serialization.dll中的,這意味著這些功能從設(shè)計(jì)之初與WCF分離開來,可以獨(dú)立使用。那么我們不如嘗試一下吧:
- 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并沒有出現(xiàn)XmlSerializer那樣傻乎乎地錯(cuò)誤。自然,與之相對的反序列化函數(shù)也很容易寫:
- let deserialize<'a> xml =
- let serializer = new DataContractSerializer(typeof<'a>)
- let textReader = new StringReader(xml)
- let xmlReader = new XmlTextReader(textReader)
- serializer.ReadObject(xmlReader) :?> 'a
試驗(yàn)一下,看看效果?
- let post = new XmlSerialization.Post()
- post.Title <- "Hello"
- post.Content <- "World"
- post.Tags <- [| "Hello"; "World" |]
- let xml = XmlSerialization.serialize post
- let post' = XmlSerialization.deserialize
xml
經(jīng)過更多試驗(yàn),我發(fā)現(xiàn)DataContractSerializer對于復(fù)雜類型的字段也可以正常應(yīng)對,而得到這些功能也只需要在目標(biāo)類型上標(biāo)記一個(gè)SerializableAttribute就行了,更細(xì)節(jié)的控制也可以通過DataContractAttribute等進(jìn)行控制。這樣看來,XmlSerializer似乎已經(jīng)可以退出歷史舞臺了?
新聞標(biāo)題:詳解F#對象序列化為XML的實(shí)現(xiàn)方法
當(dāng)前路徑:http://www.dlmjj.cn/article/dpipdcj.html


咨詢
建站咨詢
