热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

一个关于解决序列化问题的编程技巧

在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的API实现对上下文(Context)信息的统一管理。这个框架同时支持Web和GUI应用,并支持跨线程传递和跨域传递(这里指在WCF服务调用中实现客户端到服务端隐式传递),以及对上下文项目(Cont

在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的API实现对上下文(Context)信息的统一管理。这个框架同时支持Web和GUI应用,并支持跨线程传递和跨域传递(这里指在WCF服务调用中实现客户端到服务端隐式传递),以及对上下文项目(Cont

在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的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(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 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(contextItem1, "context.xml");
   6:     var contextItem2 = Deserialize("context.xml");           
   7: }

序列化操作能够正常执行,但当程序执行到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元素的先后顺序决定了反序列化的顺序。

   1: <ContextItem xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com">
   2:     <Key>__userIdKey>
   3:     <ReadOnly>trueReadOnly>
   4:     <Value xmlns:d2p1="http://www.w3.org/2001/XMLSchema" i:type="d2p1:string">FooValue>
   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:&#160; 
   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属性设置成&#8220;Value&#8221;。

   1: [DataContract(Namespace = "http://www.artech.com")]
   2: public class ContextItem
   3: {
   4:     [DataMember]
   5:     public string Key { get; private set; }
   6:&#160; 
   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序列化的序列化规则的理解。

作者:Artech
出处:http://artech.cnblogs.com

推荐阅读
  • Python处理Word文档的高效技巧
    本文详细介绍了如何使用Python处理Word文档,涵盖从基础操作到高级功能的各种技巧。我们将探讨如何生成文档、定义样式、提取表格数据以及处理超链接和图片等内容。 ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • 理解UML的重要性及其应用
    探讨为什么大多数开发人员难以成为架构师,介绍从现实世界到业务模型的抽象过程,并详细解释UML在软件设计中的关键作用。 ... [详细]
  • ABBYY FineReader:高效PDF转换、精准OCR识别与文档对比工具
    在处理PDF转换和OCR识别时,您是否遇到过格式混乱、识别率低或图表无法正常识别的问题?ABBYY FineReader以其强大的功能和高精度的识别技术,完美解决这些问题,帮助您轻松找到最终版文档。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • HTML基础入门指南
    本文将深入浅出地介绍HTML的基础知识,包括其定义、开发工具、制定机构、特性、基本标签及更多实用内容。 ... [详细]
  • 深入解析AUTOSAR方法论:汽车电子系统开发的理论基础(第三部分)
    本文详细探讨了AUTOSAR方法论在汽车电子软件系统开发中的应用,涵盖了从系统配置到生成可执行代码的各个关键步骤。通过介绍各阶段的任务和工具支持,帮助读者全面理解AUTOSAR的设计流程。 ... [详细]
  • ssm框架整合及工程分层1.先创建一个新的project1.1配置pom.xml ... [详细]
  • 本文探讨了如何在Classic ASP中实现与PHP的hash_hmac('SHA256', $message, pack('H*', $secret))函数等效的哈希生成方法。通过分析不同实现方式及其产生的差异,提供了一种使用Microsoft .NET Framework的解决方案。 ... [详细]
  • Spring Boot 中静态资源映射详解
    本文深入探讨了 Spring Boot 如何简化 Web 应用中的静态资源管理,包括默认的静态资源映射规则、WebJars 的使用以及静态首页的处理方法。通过本文,您将了解如何高效地管理和引用静态资源。 ... [详细]
  • SpringMVC RestTemplate的几种请求调用(转)
    SpringMVCRestTemplate的几种请求调用(转),Go语言社区,Golang程序员人脉社 ... [详细]
  • ListView简单使用
    先上效果:主要实现了Listview的绑定和点击事件。项目资源结构如下:先创建一个动物类,用来装载数据:Animal类如下:packagecom.example.simplelis ... [详细]
  • 本文详细介绍了Java中实现异步调用的多种方式,包括线程创建、Future接口、CompletableFuture类以及Spring框架的@Async注解。通过代码示例和深入解析,帮助读者理解并掌握这些技术。 ... [详细]
  • ArcXML:互联网空间数据交换的专用语言
    ArcXML是一种专为ArcIMS平台设计的数据交换协议,基于XML标准,用于在不同组件之间传输和描述地理空间数据。本文将详细介绍ArcXML的背景、用途及其与XML的关系。 ... [详细]
author-avatar
奔跑的人儿
很傲娇的做一个家伙!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有