一个关于解决序列化问题的编程技巧
在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的API实现对上下文(Context)信息的统一管理。这个框架同时支持Web和GUI应用,并支持跨线程传递和跨域传递(这里指在WCF服务调用中实现客户端到服务端隐式传递),以及对上下文项目(Cont
在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的API实现对上下文(Context)信息的统一管理。这个框架同时支持Web和GUI应用,并支持跨线程传递和跨域传递(这里指在WCF服务调用中实现客户端到服务端隐式传递),以及对上下文项目(ContextItem)的读写控制。关键就在于后面两个特性的支持上面,出现一个小小的关于序列化的问题。解决方案只需要改动短短的一行代码,结果却让我折腾了老半天。
一、问题重现
为了重现我实际遇到的问题,我特意将问题简化,为此我写了一个简单的例子(你可以从这里下载)。在下面的代码片断中,我创建了一个名称为ContextItem的类型,代表一个需要维护的上下文项。由于需要在WCF服务调用实现自动传递,我将起定义成DataContract。ContextItem包含Key,Value和ReadOnly三个属性,不用说ReadOnly表示该ContextItem可以被修改。注意Value属性Set方法的定义——如果ReadOnly则抛出异常。
<span> 1:</span> [DataContract(Namespace = <span>"http://www.artech.com"</span>)]
<span> 2:</span> <span>public</span> <span>class</span> ContextItem
<span> 3:</span> {
<span> 4:</span> <span>private</span> <span>object</span> <span>value</span> = <span>null</span>;
<span> 5:</span> [DataMember]
<span> 6:</span> <span>public</span> <span>string</span> Key { get; <span>private</span> set; }
<span> 7:</span> [DataMember]
<span> 8:</span> <span>public</span> <span>object</span> Value
<span> 9:</span> {
<span> 10:</span> get
<span> 11:</span> {
<span> 12:</span> <span>return</span> <span>this</span>.<span>value</span>;
<span> 13:</span> }
<span> 14:</span> set
<span> 15:</span> {
<span> 16:</span> <span>if</span> (<span>this</span>.ReadOnly)
<span> 17:</span> {
<span> 18:</span> <span>throw</span> <span>new</span> InvalidOperationException(<span>"Cannot change the value of readonly context item."</span>);
<span> 19:</span> }
<span> 20:</span> <span>this</span>.<span>value</span> = <span>value</span>;
<span> 21:</span> }
<span> 22:</span> }
<span> 23:</span> [DataMember]
<span> 24:</span> <span>public</span> <span>bool</span> ReadOnly { get; set; }
<span> 25:</span> <span>public</span> ContextItem(<span>string</span> key, <span>object</span> <span>value</span>)
<span> 26:</span> {
<span> 27:</span> <span>if</span> (<span>string</span>.IsNullOrEmpty(key))
<span> 28:</span> {
<span> 29:</span> <span>throw</span> <span>new</span> ArgumentNullException(<span>"key"</span>);
<span> 30:</span> }
<span> 31:</span> <span>this</span>.Key = key;
<span> 32:</span> <span>this</span>.Value = <span>value</span>;
<span> 33:</span> }
<span> 34:</span> }
为了演示序列化和反序列化,我写了如下两个静态的帮助方法。Serialize和Deserialize分别用于序列化和反序列化,前者将对象序列成成XML并保存到指定的文件中,后者则从文件读取XML并反序列化成相应的对象。
<span> 1:</span> <span>public</span> <span>static</span> T Deserialize<t>(<span>string</span> fileName)</t>
<span> 2:</span> {
<span> 3:</span> DataContractSerializer serializer = <span>new</span> DataContractSerializer(<span>typeof</span>(T));
<span> 4:</span> <span>using</span> (XmlReader reader = <span>new</span> XmlTextReader(fileName))
<span> 5:</span> {
<span> 6:</span> <span>return</span> (T)serializer.ReadObject(reader);
<span> 7:</span> }
<span> 8:</span> }
<span> 9:</span>
<span> 10:</span> <span>public</span> <span>static</span> <span>void</span> Serialize<t>(T instance, <span>string</span> fileName)</t>
<span> 11:</span> {
<span> 12:</span> DataContractSerializer serializer = <span>new</span> DataContractSerializer(<span>typeof</span>(T));
<span> 13:</span> <span>using</span> (XmlWriter writer = <span>new</span> XmlTextWriter(fileName, Encoding.UTF8))
<span> 14:</span> {
<span> 15:</span> serializer.WriteObject(writer, instance);
<span> 16:</span> }
<span> 17:</span> Process.Start(fileName);
<span> 18:</span> }
我们的程序很简单。从如下的代码片断中,我们先创建一个ContextItem对象,然后将ReadOnly属性设置成true。然后调用Serialize方法将对象序列化成XML并保存在一个名称为context.xml的文件中。然后调用Deserialize方法,读取该文件进行反序列化。
<span> 1:</span> <span>static</span> <span>void</span> Main(<span>string</span>[] args)
<span> 2:</span> {
<span> 3:</span> var contextItem1 = <span>new</span> ContextItem(<span>"__userId"</span>, <span>"Foo"</span>);
<span> 4:</span> contextItem1.ReadOnly = <span>true</span>;
<span> 5:</span> Serialize<contextitem>(contextItem1, <span>"context.xml"</span>);</contextitem>
<span> 6:</span> var contextItem2 = Deserialize<contextitem>(<span>"context.xml"</span>); </contextitem>
<span> 7:</span> }
序列化操作能够正常执行,但当程序执行到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元素的先后顺序决定了反序列化的顺序。
<span> 1:</span> <span><span>ContextItem</span> <span>xmlns:i</span><span>="http://www.w3.org/2001/XMLSchema-instance"</span> <span>xmlns</span><span>="http://www.artech.com"</span><span>></span></span>
<span> 2:</span> <span><span>Key</span><span>></span>__userId<span></span><span>Key</span><span>></span></span>
<span> 3:</span> <span><span>ReadOnly</span><span>></span>true<span></span><span>ReadOnly</span><span>></span></span>
<span> 4:</span> <span><span>Value</span> <span>xmlns:d2p1</span><span>="http://www.w3.org/2001/XMLSchema"</span> <span>i:type</span><span>="d2p1:string"</span><span>></span>Foo<span></span><span>Value</span><span>></span></span>
<span> 5:</span> <span></span><span>ContextItem</span><span>></span>
在上面的例子中,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属性将会迫使该属性被反序列化。
<span> 1:</span> [DataContract(Namespace = <span>"http://www.artech.com"</span>)]
<span> 2:</span> <span>public</span> <span>class</span> ContextItem
<span> 3:</span> {
<span> 4:</span> <span>private</span> <span>object</span> <span>value</span> = <span>null</span>;
<span> 5:</span> <span>private</span> <span>bool</span> readOnly;
<span> 6:</span> [DataMember]
<span> 7:</span> <span>public</span> <span>string</span> Key { get; <span>private</span> set; }
<span> 8:</span>
<span> 9:</span> [DataMember(Order = 1)]
<span> 10:</span> <span>public</span> <span>object</span> Value
<span> 11:</span> {
<span> 12:</span> get
<span> 13:</span> {
<span> 14:</span> <span>return</span> <span>this</span>.<span>value</span>;
<span> 15:</span> }
<span> 16:</span> set
<span> 17:</span> {
<span> 18:</span> <span>if</span> (<span>this</span>.readOnly)
<span> 19:</span> {
<span> 20:</span> <span>throw</span> <span>new</span> InvalidOperationException(<span>"Cannot change the value of readonly context item."</span>);
<span> 21:</span> }
<span> 22:</span> <span>this</span>.<span>value</span> = <span>value</span>;
<span> 23:</span> }
<span> 24:</span> }
<span> 25:</span> [DataMember(Order =2)]
<span> 26:</span> <span>public</span> <span>bool</span> ReadOnly
<span> 27:</span> {
<span> 28:</span> get
<span> 29:</span> {
<span> 30:</span> <span>return</span> readOnly;
<span> 31:</span> }
<span> 32:</span> set
<span> 33:</span> {
<span> 34:</span> readOnly = <span>value</span>;
<span> 35:</span> }
<span> 36:</span> }
<span> 37:</span> <span>//Others</span>
<span> 38:</span> }
有兴趣的读者可以亲自试试看,如果我们进行了如上的更改,前面的程序就能正常运行了。到这里,有的读者可以要问了,你不是说仅仅有一行代码的变化吗,我看上面改动的不止一行嘛。没有错,我们完全可以作更少的更改来解决问题。
四、解决方案二:将数据成员定义在字段上而不是属性上
我们再换一种思维,之所以出现异常是在反序列化的时候调用Value属性的Set方法所致。如果在反序列化的时候不调用这个方法不就得了吗?那么,如何才能避免对Value属性的Set方法的调用呢?方法很简单,那就是将数据成员定义在字段上,而不是属性上。基于属性的数据成员在反序列化的时候不得不通过调用Set方法对数据项进行初始化,而基于字段的数据成员在反序列化的时候只需要直接对其复制就可以了。
基于这样的思路,我们对原来的ContextItem进行简单的改动——将DataMemberAttribute特性从Value属性移到value字段上。需要注意的,为了符合于原来的Schema,需要将DataMemberAttribute特性的Name属性设置成“Value”。
<span> 1:</span> [DataContract(Namespace = <span>"http://www.artech.com"</span>)]
<span> 2:</span> <span>public</span> <span>class</span> ContextItem
<span> 3:</span> {
<span> 4:</span> [DataMember]
<span> 5:</span> <span>public</span> <span>string</span> Key { get; <span>private</span> set; }
<span> 6:</span>
<span> 7:</span> [DataMember(Name = <span>"Value"</span>)]
<span> 8:</span> <span>private</span> <span>object</span> <span>value</span> = <span>null</span>;
<span> 9:</span> <span>public</span> <span>object</span> Value
<span> 10:</span> {
<span> 11:</span> get
<span> 12:</span> {
<span> 13:</span> <span>return</span> <span>this</span>.<span>value</span>;
<span> 14:</span> }
<span> 15:</span> set
<span> 16:</span> {
<span> 17:</span> <span>if</span> (<span>this</span>.ReadOnly)
<span> 18:</span> {
<span> 19:</span> <span>throw</span> <span>new</span> InvalidOperationException(<span>"Cannot change the value of readonly context item."</span>);
<span> 20:</span> }
<span> 21:</span> <span>this</span>.<span>value</span> = <span>value</span>;
<span> 22:</span> }
<span> 23:</span> }
<span> 24:</span> [DataMember]
<span> 25:</span> <span>public</span> <span>bool</span> ReadOnly { get; set; }
<span> 26:</span> <span>//Others</span>
<span> 27:</span> }
<span> 28:</span> }
总结
虽然这仅仅是一个很小的问题,解决的方案看起来也是如此的简单。但是,这并不意味着这是一个可以被忽视的问题,背后隐藏对DataMemberAttribute序列化的序列化规则的理解。
作者:Artech
出处:http://artech.cnblogs.com

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Win11系統無法安裝中文語言包的解決方法隨著Windows11系統的推出,許多用戶開始升級他們的作業系統以體驗新的功能和介面。然而,一些用戶在升級後發現他們無法安裝中文語言包,這給他們的使用體驗帶來了困擾。在本文中,我們將探討Win11系統無法安裝中文語言套件的原因,並提供一些解決方法,幫助使用者解決這個問題。原因分析首先,讓我們來分析一下Win11系統無法

隨著智慧型手機技術的不斷發展,手機在我們日常生活中扮演著越來越重要的角色。而作為一款專注於遊戲效能的旗艦手機,黑鯊手機備受玩家青睞。然而,有時候我們也會面臨到黑鯊手機開不了機的情況,這時候我們就需要採取一些措施來解決這個問題。接下來,就讓我們來分享五招教你解決黑鯊手機開不了機的問題:第一招:檢查電池電量首先,確保你的黑鯊手機有足夠的電量。可能是因為手機電量耗盡

隨著社群媒體的不斷發展,小紅書已經成為越來越多年輕人分享生活、發現美好事物的平台。許多用戶在發布圖片時遇到了自動儲存的問題,這讓他們感到十分困擾。那麼,如何解決這個問題呢?一、小紅書發布自動儲存圖片怎麼解決? 1.清除快取首先,我們可以嘗試清除小紅書的快取資料。步驟如下:(1)開啟小紅書,點選右下角的「我的」按鈕;(2)在個人中心頁面,找到「設定」並點選;(3)向下捲動,找到「清除快取」選項,點擊確認。清除快取後,重新進入小紅書,嘗試發布圖片看是否解決了自動儲存的問題。 2.更新小紅書版本確保你的小

大家都知道,如果電腦無法載入驅動程序,該設備可能就無法正常工作或與電腦進行正確的互動。那在電腦上彈出無法在此裝置上載入驅動程式的提示框,我們要如何解決呢?下面小編就教大家兩招輕鬆解決問題。 無法在此裝置上載入驅動程式解決方法 1、開始功能表搜尋「核心隔離」。 2、將記憶體完整性關閉,上方提示「記憶體完整性已關閉。你的裝置可能易受攻擊。」點擊後方忽略即可,不會對使用有影響。 3.重啟機器之後即可解決問題。

標題:分析Oracle錯誤3114:原因及解決方法在使用Oracle資料庫時,常常會遇到各種錯誤代碼,其中錯誤3114是比較常見的一個。此錯誤一般涉及資料庫連結的問題,可能導致存取資料庫時出現異常狀況。本文將對Oracle錯誤3114進行解讀,探討其造成的原因,並給出解決該錯誤的具體方法以及相關的程式碼範例。 1.錯誤3114的定義Oracle錯誤3114通

一、今日頭條發布文章怎麼有收益?今日頭條發布文章獲得更多收益方法! 1.開通基礎權益:原創文章選擇投放廣告可獲得收益,影片必須原創橫屏才會有收益。 2.開通百粉權益:粉絲量達百粉以上,微頭條、原創問答創作及問答均可獲得收益。 3.堅持原創作品:原創作品包含文章、微標題及問題等,要求300字以上。注意違規抄襲作品作為原創發布,會被扣信用分,即使有收益也會被扣除。 4.垂直度:做專業領域一類的文章,不能隨意跨領域寫文章,會得不到合適的推薦,達不到作品的專和精,難以吸引粉絲讀者。 5.活躍度:活躍度高,

WordPress後台亂碼煩惱?試試這些解決方案,需要具體程式碼範例隨著WordPress在網站建置中的廣泛應用,許多用戶可能會遇到WordPress後台亂碼的問題。這種問題會導致後台管理介面顯示亂碼,對使用者的使用帶來極大困擾。本文將介紹一些常見的解決方案,幫助使用者解決WordPress後台亂碼的煩惱。修改wp-config.php檔案開啟wp-config.

黑鯊手機是一款以效能強悍、遊戲體驗優異而聞名的智慧型手機品牌,備受廣大遊戲玩家和科技愛好者的喜愛。然而,就像其他智慧型手機一樣,黑鯊手機也會出現各種問題,其中充電故障是比較常見的一種。充電故障不僅會影響手機的正常使用,還可能引發更嚴重的問題,因此及時解決充電問題十分重要。本文將從常見的黑鯊手機充電故障原因入手,介紹追蹤與解決充電問題的方法,希望能幫助讀者解決黑鯊
