搜索
首页数据库mysql教程一个关于解决序列化问题的编程技巧

在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的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

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
golang 报错:“undeclared name…” 如何解决?golang 报错:“undeclared name…” 如何解决?Jun 24, 2023 pm 03:31 PM

Golang(Go编程语言)是一种基于C语言的编程语言,被广泛用于Web开发、网络编程、操作系统等领域。然而,在编写Golang程序时经常会遇到一个常见的问题,就是“undeclaredname”(未声明名称)错误。下面将介绍如何解决这个问题。了解错误信息在编译和运行Golang程序时,如果遇到了未声明名称错误,会在控制台输出相应的错误信

Java中的ClassNotFoundException——找不到类要怎么解决?Java中的ClassNotFoundException——找不到类要怎么解决?Jun 25, 2023 am 08:30 AM

Java中的ClassNotFoundException是一种常见的编译错误。当我们尝试使用Java虚拟机(JVM)加载某个类时,如果JVM找不到该类,就会抛出ClassNotFoundException。这个错误可能出现在程序运行时,也可能出现在编译时。在本文中,我们将讨论什么是ClassNotFoundException,它为什么会发生以及如何解决它。C

Java错误:JDBC错误,如何解决和避免Java错误:JDBC错误,如何解决和避免Jun 24, 2023 pm 02:40 PM

随着Java的广泛应用,Java程序在连接数据库时经常会出现JDBC错误。JDBC(JavaDatabaseConnectivity)是Java中用于连接数据库的编程接口,因此,JDBC错误是在Java程序与数据库交互时遇到的一种错误。下面将介绍一些最常见的JDBC错误及如何解决和避免它们。ClassNotFoundException这是最常见的JDBC

golang 编译错误:"undefined: json.Marshal" 如何解决?golang 编译错误:"undefined: json.Marshal" 如何解决?Jun 24, 2023 pm 03:24 PM

Go语言是一门越来越受欢迎的编程语言,它的简洁、高效、易于编写的特点已经被越来越多的开发者所认可。而在Go语言开发中,遇到编译错误是不可避免的。其中一个常见的错误就是“undefined:json.Marshal”。这个错误通常发生在你使用了Go标准库的“encoding/json”包时,编译器提示找不到“json.Marshal”的定义。这个问题的根本原

golang 报错:“undefined variable or function” 如何解决?golang 报错:“undefined variable or function” 如何解决?Jun 24, 2023 pm 05:18 PM

Go语言作为一门快速发展的编程语言,被广泛应用于各种项目和领域。然而,在使用golang编写程序时,你有可能会遇到一些报错,其中一个常见的报错是“undefinedvariableorfunction”。那么,这个错误是什么意思?它是如何产生的?又该如何解决呢?本文将会对这些问题进行探讨。首先,我们需要了解一些基本概念。在golang中,变量和函数是两

在Vue应用中遇到“SyntaxError: Unexpected token”怎么解决?在Vue应用中遇到“SyntaxError: Unexpected token”怎么解决?Jun 24, 2023 pm 06:55 PM

在Vue应用中遇到“SyntaxError:Unexpectedtoken”怎么解决?Vue是前端开发中广泛使用的一个JavaScript框架,它可以让我们更轻松地管理页面的状态、渲染和交互。但是在编写Vue应用时,有时会遇到“SyntaxError:Unexpectedtoken”报错,这个错误提示意味着代码中存在语法错误,JavaScript引擎

golang 报错:“invalid use of , operator” 如何解决?golang 报错:“invalid use of , operator” 如何解决?Jun 24, 2023 pm 07:15 PM

近年来,Golang一直受到越来越多开发者的青睐。但是,即使是最有经验的开发人员也会遇到一些挫折,比如一些报错。其中,一种常见的报错是:“invaliduseof,operator”。在这篇文章中,我将为大家介绍这个报错的原因,以及解决方法。首先,我们需要了解什么是","操作符。在Golang中,","操作符通常被用来在数组、参数列表或结构体中分隔不

Java错误:Gradle错误,如何解决和避免Java错误:Gradle错误,如何解决和避免Jun 25, 2023 am 11:13 AM

Java作为目前最受欢迎的编程语言之一,在开发过程中常常会遇到各种各样的错误,其中Gradle错误是比较常见的一种。本文将介绍如何解决和避免Gradle错误。一、Gradle错误的原因Gradle为构建工具,其主要作用是将代码、资源文件、第三方库等打包并生成可执行的应用程序。在实际开发中,如果不注意一些细节,很容易出现Gradle错误,主要原因通常有以下几种

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器