一个关于解决序列化问题的编程技巧
在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的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")] 3: { 5: [DataMember] 7: [DataMember] 9: { 11: { 13: } 15: { 17: { 19: } 22: } 24: bool ReadOnly { get; set; }
26: { 28: { 30: } 32: this.Value = 33: }
我们的程序很简单。从如下的代码片断中,我们先创建一个ContextItem对象,然后将ReadOnly属性设置成true。然后调用Serialize方法将对象序列化成XML并保存在一个名称为context.xml的文件中。然后调用Deserialize方法,读取该文件进行反序列化。 3: var contextItem1 = new ContextItem("__userId",1)">"Foo");
5: Serialize<ContextItem>(contextItem1,1)">"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元素的先后顺序决定了反序列化的顺序。 2: Key>__userId</>
5: bool readOnly;
7: private set; }
9: [DataMember(Order = 1)] 12: get 14: 15: }
17: { 19: { 22: 23: }
25: [DataMember(Order =2)] 27: { 29: { 31: } 33: { 36: } 38: } 有兴趣的读者可以亲自试试看,如果我们进行了如上的更改,前面的程序就能正常运行了。到这里,有的读者可以要问了,你不是说仅仅有一行代码的变化吗,我看上面改动的不止一行嘛。没有错,我们完全可以作更少的更改来解决问题。 四、解决方案二:将数据成员定义在字段上而不是属性上我们再换一种思维,之所以出现异常是在反序列化的时候调用Value属性的Set方法所致。如果在反序列化的时候不调用这个方法不就得了吗?那么,如何才能避免对Value属性的Set方法的调用呢?方法很简单,那就是将数据成员定义在字段上,而不是属性上。基于属性的数据成员在反序列化的时候不得不通过调用Set方法对数据项进行初始化,而基于字段的数据成员在反序列化的时候只需要直接对其复制就可以了。 基于这样的思路,我们对原来的ContextItem进行简单的改动——将DataMemberAttribute特性从Value属性移到value字段上。需要注意的,为了符合于原来的Schema,需要将DataMemberAttribute特性的Name属性设置成“Value”。 6:?
9: object Value
11: get 13: value;
15: set 17: this.ReadOnly)
19: "Cannot change the value of readonly context item.");
21: 22: }
24: [DataMember] 26: //Others
28: } 总结虽然这仅仅是一个很小的问题,解决的方案看起来也是如此的简单。但是,这并不意味着这是一个可以被忽视的问题,背后隐藏对DataMemberAttribute序列化的序列化规则的理解。
作者:Artech
出处:http://artech.cnblogs.com 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 如何在IIS 10上承载ASP.NET Web API 2项目
- asp.net – 为WebAPI操作设置默认的Media Formatter
- asp.net-mvc – 如何检测移动浏览器,并将适当的内容指向它?
- asp.net – 当通过文件上传控件上传文件时,在c#.net中重命名
- asp.net – 为什么aspnet_users使用guid来代替id而不是增加
- .Net Core微服务入门全纪录(二)——Consul-服务注册与发现
- asp.net-mvc-3 – 什么冒号(:)意味着在c#中定义一个类?
- asp.net-web-api – 为WCF REST和ASP.NET Web API共享相同的
- asp.net-mvc – 扩展SignalR Server Hub类以创建基本Hub类
- asp.net-mvc – 在asp.net mvc中的redirecttoaction中设置查
- asp.net – 如何基于客户端硬件或其他解决方案实
- asp.net-mvc – 我的剃刀视图的自定义基页类型,如
- asp.net-mvc-3 – 创建MVC3剃刀助手,如Helper.Be
- ASP.NET Web API为单个路由定制IHttpControllerS
- asp.net – Visual Studio 2010:将网站项目转换
- asp.net-mvc – Asp.net Identity使用什么算法来
- asp.net – Mono有什么不适合的东西?
- asp.net – 清除AjaxToolkit AsyncFileUpload控件
- 实体框架 – 在每个单元测试之前重新创建和重新设
- 如何使ASP.NET MVC应用程序多语言?