一個關于解決序列化問題的編程技巧

在前一篇文章中我曾經說過,現在正在做一個小小的框架以實現采用統一的API實現對上下文(Context)信息的統一管理。這個框架同時支持Web和GUI應用,并支持跨線程傳遞和跨域傳遞(這里指在WCF服務調用中實現客戶端到服務端隱式傳遞),以及對上下文項目(ContextItem)的讀寫控制。關鍵就在于后面兩個特性的支持上面,出現一個小小的關于序列化的問題。解決方案只需要改動短短的一行代碼,結果卻讓我折騰了老半天。

一、問題重現

為了重現我實際遇到的問題,我特意將問題簡化,為此我寫了一個簡單的例子(你可以從這里下載)。在下面的代碼片斷中,我創建了一個名稱為ContextItem的類型,代表一個需要維護的上下文項。由于需要在WCF服務調用實現自動傳遞,我將起定義成DataContract。ContextItem包含Key,Value和ReadOnly三個屬性,不用說ReadOnly表示該ContextItem可以被修改。注意Value屬性Set方法的定義——如果ReadOnly則拋出異常。

   1: [DataContract(Namespace = "http://www.artech.com")]
   2: public class ContextItem
   3: {
   4:     private object value = null;
   5:     [DataMember]
   6:     public string Key { get; private set; }
   7:     [DataMember]
   8:     public object Value
   9:     {
  10:         get
  11:         {
  12:             return this.value;
  13:         }
  14:         set
  15:         {
  16:             if (this.ReadOnly)
  17:             {
  18:                 throw new InvalidOperationException("Cannot change the value of readonly context item.");
  19:             }
  20:             this.value = value;
  21:         }
  22:     }
  23:     [DataMember]
  24:     public bool ReadOnly { get; set; }
  25:     public ContextItem(string key, object value)
  26:     {
  27:         if (string.IsNullOrEmpty(key))
  28:         {
  29:             throw new ArgumentNullException("key");
  30:         }
  31:         this.Key = key;
  32:         this.Value = value;
  33:     }
  34: }

為了演示序列化和反序列化,我寫了如下兩個靜態的幫助方法。Serialize和Deserialize分別用于序列化和反序列化,前者將對象序列成成XML并保存到指定的文件中,后者則從文件讀取XML并反序列化成相應的對象。

   1: public static T Deserialize<T>(string fileName)
   2: {
   3:     DataContractSerializer serializer = new DataContractSerializer(typeof(T));
   4:     using (XmlReader reader = new XmlTextReader(fileName))
   5:     {
   6:         return (T)serializer.ReadObject(reader);
   7:     }
   8: }
   9:? 
  10: public static void Serialize<T>(T instance, string fileName)
  11: {
  12:     DataContractSerializer serializer = new DataContractSerializer(typeof(T));
  13:     using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
  14:     {
  15:         serializer.WriteObject(writer, instance);
  16:     } 
  17:     Process.Start(fileName);
  18: }

我們的程序很簡單。從如下的代碼片斷中,我們先創建一個ContextItem對象,然后將ReadOnly屬性設置成true。然后調用Serialize方法將對象序列化成XML并保存在一個名稱為context.xml的文件中。然后調用Deserialize方法,讀取該文件進行反序列化。

   1: static void Main(string[] args)
   2: {
   3:     var contextItem1 = new ContextItem("__userId", "Foo");
   4:     contextItem1.ReadOnly = true;
   5:     Serialize<ContextItem>(contextItem1, "context.xml");
   6:     var contextItem2 = Deserialize<ContextItem>("context.xml");           
   7: }

序列化操作能夠正常執行,但當程序執行到Deserialize的時候拋出如下一個InvalidOperationException異常。

image

二、問題分析

從上面給出的截圖,我們不難看出,異常是在給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元素的先后順序決定了反序列化的順序。

   1: <ContextItem xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com">
   2:     <Key>__userId</Key>
   3:     <ReadOnly>true</ReadOnly>
   4:     <Value xmlns:d2p1="http://www.w3.org/2001/XMLSchema" i:type="d2p1:string">Foo</Value>
   5: </ContextItem>

在上面的例子中,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屬性將會迫使該屬性被反序列化。

   1: [DataContract(Namespace = "http://www.artech.com")]
   2: public class ContextItem
   3: {
   4:     private object value = null;
   5:     private bool readOnly;
   6:     [DataMember]
   7:     public string Key { get; private set; }
   8:? 
   9:     [DataMember(Order = 1)]
  10:     public object Value
  11:     {
  12:         get
  13:         {
  14:             return this.value;
  15:         }
  16:         set
  17:         {
  18:             if (this.readOnly)
  19:             {
  20:                 throw new InvalidOperationException("Cannot change the value of readonly context item.");
  21:             }
  22:             this.value = value;
  23:         }
  24:     }
  25:     [DataMember(Order =2)]
  26:     public bool ReadOnly
  27:     {
  28:         get
  29:         {
  30:             return readOnly;
  31:         }
  32:         set
  33:         {
  34:             readOnly = value;
  35:         }
  36:     }
  37:     //Others
  38: }

有興趣的讀者可以親自試試看,如果我們進行了如上的更改,前面的程序就能正常運行了。到這里,有的讀者可以要問了,你不是說僅僅有一行代碼的變化嗎,我看上面改動的不止一行嘛。沒有錯,我們完全可以作更少的更改來解決問題。

四、解決方案二:將數據成員定義在字段上而不是屬性上

我們再換一種思維,之所以出現異常是在反序列化的時候調用Value屬性的Set方法所致。如果在反序列化的時候不調用這個方法不就得了嗎?那么,如何才能避免對Value屬性的Set方法的調用呢?方法很簡單,那就是將數據成員定義在字段上,而不是屬性上。基于屬性的數據成員在反序列化的時候不得不通過調用Set方法對數據項進行初始化,而基于字段的數據成員在反序列化的時候只需要直接對其復制就可以了。

基于這樣的思路,我們對原來的ContextItem進行簡單的改動——將DataMemberAttribute特性從Value屬性移到value字段上。需要注意的,為了符合于原來的Schema,需要將DataMemberAttribute特性的Name屬性設置成“Value”。

   1: [DataContract(Namespace = "http://www.artech.com")]
   2: public class ContextItem
   3: {
   4:     [DataMember]
   5:     public string Key { get; private set; }
   6:? 
   7:     [DataMember(Name = "Value")]
   8:     private object value = null;
   9:     public object Value
  10:     {
  11:         get
  12:         {
  13:             return this.value;
  14:         }
  15:         set
  16:         {
  17:             if (this.ReadOnly)
  18:             {
  19:                 throw new InvalidOperationException("Cannot change the value of readonly context item.");
  20:             }
  21:             this.value = value;
  22:         }
  23:     }
  24:     [DataMember]
  25:     public bool ReadOnly { get; set; }     
  26:      //Others
  27:     }
  28: }

總結

雖然這僅僅是一個很小的問題,解決的方案看起來也是如此的簡單。但是,這并不意味著這是一個可以被忽視的問題,背后隱藏對DataMemberAttribute序列化的序列化規則的理解。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/257174.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257174.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257174.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

踩坑之路anaconda創建虛擬環境

渾渾噩噩的過了三年渣碩生涯&#xff0c;雖然說自己是搞圖像的&#xff0c;但基本是一些機器視覺的東西&#xff0c;最近突然想好好搞搞深度學習這方面&#xff0c;想著那就先搭搭環境跑個demo吧&#xff0c;經歷了好多莫名其妙的踩坑操作&#xff0c;demo跑的終于沒bug了&…

IP多播技術及其應用

隨著全球互聯網&#xff08;Internet&#xff09;的迅猛發展&#xff0c;上網人數正以幾何級數快速增長&#xff0c;以因特網技術為主導的數據通信在通信業務總量中的比列迅速上升&#xff0c;因特網業務已成為多媒體通信業中發展最為迅速、競爭最為激烈的領域。Internet網絡傳…

【轉載】惱人的函數指針(一)

本文轉載自: http://www.cnblogs.com/AnnieKim/archive/2011/11/20/2255813.html#undefined> 這篇是為了加深記憶所寫。發現&#xff0c;很多知識若不經過反復的琢磨和動手實踐&#xff0c;是很難記得住的。 1&#xff09; 函數指針的初始化。 函數如下&#xff1a; int Com…

dns服務器未響應

昨天還好好的&#xff0c;今天打開電腦顯示DNS服務器為響應。 解決辦法&#xff1a;右擊電腦下方圖標欄——打開Windows任務管理器——服務——服務&#xff08;s&#xff09;——找到DNS client和DHCP client——右擊重啟

php分頁原理

<?php 1.分頁原理所需數據&#xff1a; 總記錄數&#xff1a; $records mysql_num_rows() 每頁顯示&#xff1a; $pagesize 人為定義10 總頁數&#xff1a; $pages $records/$pagesize 當前頁&#xff1a; $page 自己選擇2.分頁的sql語句&#xff1a; SELECT * F…

從客戶端(CourseIssueContent=P財務審計師崗位認證招生簡章BR...)中檢測到有潛在危險的 Request.Form 值。...

說明: 請求驗證過程檢測到有潛在危險的客戶端輸入值&#xff0c;對請求的處理已經中止。該值可能指示危及應用程序安全的嘗試&#xff0c;如跨站點的腳本攻擊。通過在 Page 指令或 配置節中設置 validateRequestfalse 可以禁用請求驗證。但是&#xff0c;在這種情況下&#xff…

ubuntu安裝pytorch鏡像修改及下載

ubuntu安裝pytorch鏡像修改及下載 下載pytorch下載太慢&#xff0c;搞了很長時間&#xff0c;終于改好鏡像能快速下載了&#xff0c;記錄以下。 1.在/home/用戶名/ 下找到/.condarc 文件&#xff0c;可能需要你右擊鼠標顯示隱藏文件才能顯示&#xff0c; 2.把內容修改為清華等鏡…

R--線性回歸診斷(一)

線性回歸診斷--R 【轉載時請注明來源】&#xff1a;http://www.cnblogs.com/runner-ljt/ Ljt 勿忘初心 無畏未來 作為一個初學者&#xff0c;水平有限&#xff0c;歡迎交流指正。 在R中線性回歸&#xff0c;一般使用lm函數就可以得到線性回歸模型&#xff0c;但是得到的模型…

CSS屬性(根據繼承性分為兩類)

一、可繼承屬性 1》所有標簽可繼承&#xff1a; visibility:行高 cursor: 2》內聯標簽可繼承&#xff1a; line-height:行高 color:文字顏色 font-family:文字字體 font-size:文字大小 font-weight:文字加粗 text-decoration:文字下劃線 3》塊級標簽可繼承&#xff1a; text-in…

妙趣橫生的算法--棧和隊列

棧 棧的特點是先進后出&#xff0c;一張圖簡單介紹一下。 #include "stdio.h" #include "math.h" #include "stdlib.h" #define STACK_INIT_SIZE 20 #define STACKINCRE…

win10系統開不了機

電腦裝了雙系統&#xff0c;從ubuntu切回win10系統后&#xff0c;win10系統開不了機&#xff0c;一直轉圈&#xff0c;修復結果是什么C:\WINDOWS\System32\Logfiles\Srt\SrtTrail.txt問題&#xff0c;是了網上的常用方法都沒成功。 最后我的解決方案&#xff1a;強制關機后開機…

Android SDK打包

2015年6月18日 14:38:49 星期四 eclipse: 1. 將寫好的代碼上傳版本庫 2. 刪除 /bin/* 3. eclipse->project->clean... 4. 上一步自動生成 /bin/xx.jar 5. 復制/bin/xx.jar 到 /libs/xx.jar 6. 刪除 /src/* 7. 連同demo和剛才的工程文件夾壓縮給到對方(這樣可以避免包命…

MySQL 5.7.11 重置root密碼

1.修改/etc/my.conf&#xff0c;添加參數skip-grant-tables 2.重啟mysql service mysqld stop service mysqld start 3.用root 直接登錄 [rootbogon ~]# mysql -uroot Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 4 Server versio…

resure挽救筆記本系統和一些相關的操作記錄

使用fedora23很久了, 但是感覺不是很流暢, 出現了一些不太穩定的體驗, 所以想改到centos7. 因為centos7的很多東西 跟 fedora23 很相近了. 所以應該是無縫過渡是選擇32位的系統還是選擇64位的系統?還是要使用 32位的 它是90%的人的選擇使用, 是普通人的通用選擇, 幾乎支持linu…

2021-06-08

opencv無法讀取mp4文件opencv讀取mp4文件時&#xff0c;總是VideoCapture.isopen()返回0,即無法打開cap。解決方法&#xff0c;將opencv安裝包的opencv_videoio_ffmpeg451_64文件復制進工程中。

Web網頁布局的主要方式

一、靜態布局&#xff08;static layout&#xff09; 即傳統Web設計&#xff0c;網頁上的所有元素的尺寸一律使用px作為單位。 1、布局特點 不管瀏覽器尺寸具體是多少&#xff0c;網頁布局始終按照最初寫代碼時的布局來顯示。常規的pc的網站都是靜態&#xff08;定寬度&#xf…

HDU 3966 Aragorn's Story (樹鏈點權剖分,成段修改單點查詢)

題目鏈接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid3966 樹鏈剖分的模版&#xff0c;成段更新單點查詢。熟悉線段樹的成段更新的話就小case啦。 1 //樹鏈剖分 邊權修改 單點查詢2 #include <iostream>3 #include <cstring>4 #include <algorithm&…

微信分享無響應的解決

微信分享無響應的解決 最近使用友盟的社會化分享&#xff0c;集成到程序中進行分享功能的開發。 可是一開始還是可以正常使用&#xff0c;今天突然發現微信分享&#xff08;好友分享和朋友圈分享&#xff09;均是點擊沒有響應&#xff0c;也就是點擊后&#xff0c;沒有任何回饋…

x64電腦連接x32共享打印機

下載64位打印機驅動到64位電腦&#xff0c;在連接32位共享打印機出錯時出現在本地尋找相關inf文件&#xff0c;此時將64位打印機驅動解壓(不在64位本地安裝)并找到相應inf文件&#xff0c;載入即可連接成功。

HTML中的br標簽講解(菜鳥)

br標簽&#xff1a;如何在HTML中換行&#xff1f;可以使用br標簽 1.br標簽作用&#xff1a;換行 2.br標簽格式&#xff1a;<br/> 3.br標簽的注意點&#xff1a; 3.1多個br標簽可以連續使用&#xff0c;使用了多少個br標簽就會換多少行 3.2由于HTML的作用就是用來給文本添…