為了示範序列化和反序列化,我寫了如下兩個靜态的幫助方法。Serialize和Deserialize分别用于序列化和反序列化,前者将對象序列成成XML并儲存到指定的檔案中,後者則從檔案讀取XML并反序列化成相應的對象。
我們的程式很簡單。從如下的代碼片斷中,我們先建立一個ContextItem對象,然後将ReadOnly屬性設定成true。然後調用Serialize方法将對象序列化成XML并儲存在一個名稱為context.xml的檔案中。然後調用Deserialize方法,讀取該檔案進行反序列化。
序列化操作能夠正常執行,但當程式執行到Deserialize的時候抛出如下一個InvalidOperationException異常。

從上面給出的截圖,我們不難看出,異常是在給ContextItem對象的Value屬性指派的時候抛出的。如果對DataContractSerializer序列化器的序列化/反序列化規則的有所了解的話,應該知道:對于資料契約(DataContract)基于屬性(Property)的資料成員(DataMember),序列器在反序列化的時候是通過調用Set方法對其進行初始化的。在本例中,由于ReadOnly是True,在對Value進行反序列化的時候必然會調用Set方法。但是,隻讀的ContextItem卻不能對其指派,是以異常抛出。
那麼,如何來解決這個問題呢?我最初的想法是這樣:在序列化的時候将ReadOnly屬性設定成False,然後添加另一個屬性專門用于儲存真實的值。在進行反序列的時候,由于ReadOnly為false,是以不會出現異常。當反序列化完成之後,在将ReadOnly的初始值賦上。雖然上述的方案能夠解決問題,但是為此對ContextItem添加一個隻在序列化和反序列化的過程中在有用的屬性,總覺得很醜陋。
我們不妨換一種思路:異常産生于對Value屬性凡序列化時發現ReadOnly非True的情況。那麼怎樣采用避免這種情況的發生呢?如果Value屬性先于ReadOnly屬性被序列化,那麼ReadOnly的初始值就是False,這個問題不就解決了嗎?這就是我們的第一個解決方案。
那麼,如果控制那麼屬性先被反序列化,那麼後被序列化呢?這就是要了解DataContractSerializer序列化器的序列化和發序列化規則了。在預設的情況下,DataContractSerializer是按照資料成員的名稱的順序進行序列化的。這可以從生成出來的XML的結構看出來。而XML元素的先後順序決定了反序列化的順序。
在上面的例子中,ContextItem的ReadOnly排在Value的前面,會先被序列化。那麼,是不是我們要更新Value或者ReadOnly的資料成員(DataMember,不是屬性名稱)呢?這肯定不是我們想要的解決方案。在SOA的世界中,DataMember是契約的一部分,往往是不容許更改的。
如果在不更改資料成員名稱的前提下讓屬性Value先于ReadOnly被序列化,需要用到DataContractSerializer另一條反序列化規則:我們可以通過DataMemberAttribute特性的Order屬性控制序列化後的屬性在XML元素清單中的位置。
為此,我們有了答案,我們隻需要将ContextItem稍加改動就可以了。在如下的代碼中,在為Value和ReadOnly兩個屬性應用DataMemberAttribute的時候,将Order屬性分别設定成1和2,這樣就能使ContextItem對象在被序列化的時候,Value和ReadOnly屬性對應的XML元素将永遠會有前後之分。這裡還需要注意的是,在Value屬性的Set方法中,判斷是否隻讀,采用的不是ReadOnly屬性,而是對應的readonly字段。這一點非常重要,如果調用ReadOnly屬性将會迫使該屬性被反序列化。
有興趣的讀者可以親自試試看,如果我們進行了如上的更改,前面的程式就能正常運作了。到這裡,有的讀者可以要問了,你不是說僅僅有一行代碼的變化嗎,我看上面改動的不止一行嘛。沒有錯,我們完全可以作更少的更改來解決問題。
我們再換一種思維,之是以出現異常是在反序列化的時候調用Value屬性的Set方法所緻。如果在反序列化的時候不調用這個方法不就得了嗎?那麼,如何才能避免對Value屬性的Set方法的調用呢?方法很簡單,那就是将資料成員定義在字段上,而不是屬性上。基于屬性的資料成員在反序列化的時候不得不通過調用Set方法對資料項進行初始化,而基于字段的資料成員在反序列化的時候隻需要直接對其複制就可以了。
基于這樣的思路,我們對原來的ContextItem進行簡單的改動——将DataMemberAttribute特性從Value屬性移到value字段上。需要注意的,為了符合于原來的Schema,需要将DataMemberAttribute特性的Name屬性設定成“Value”。
雖然這僅僅是一個很小的問題,解決的方案看起來也是如此的簡單。但是,這并不意味着這是一個可以被忽視的問題,背後隐藏對DataMemberAttribute序列化的序列化規則的了解。