一个关于解决序列化问题的编程技巧
在前一篇文章中我曾经说过,现在正在做一个小小的框架以实现采用统一的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 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











Win11 システムに中国語言語パックをインストールできない問題の解決策 Windows 11 システムの発売に伴い、多くのユーザーは新しい機能やインターフェイスを体験するためにオペレーティング システムをアップグレードし始めました。ただし、一部のユーザーは、アップグレード後に中国語の言語パックをインストールできず、エクスペリエンスに問題が発生したことに気づきました。この記事では、Win11 システムに中国語言語パックをインストールできない理由について説明し、ユーザーがこの問題を解決するのに役立ついくつかの解決策を提供します。原因分析 まず、Win11 システムの機能不全を分析しましょう。

スマートフォン技術が発展し続けるにつれて、携帯電話は私たちの日常生活においてますます重要な役割を果たしています。 Black Shark フォンは、ゲーム パフォーマンスに重点を置いたフラッグシップ フォンとして、プレイヤーから高い支持を得ています。ただし、場合によっては、Black Shark 携帯電話の電源が入らないという状況にも直面するため、この問題を解決するために何らかの措置を講じる必要があります。次に、Black Shark 携帯電話の電源が入らない問題を解決する方法を説明する 5 つのヒントを共有しましょう: ステップ 1: バッテリー残量を確認する まず、Black Shark 携帯電話に十分な電力があることを確認します。携帯電話のバッテリーが消耗している可能性があります

ソーシャルメディアの継続的な発展に伴い、Xiaohongshu はますます多くの若者が自分たちの生活を共有し、美しいものを発見するためのプラットフォームとなっています。多くのユーザーは、画像を投稿する際の自動保存の問題に悩まされています。では、この問題をどうやって解決すればよいでしょうか? 1.小紅書で公開するときに写真が自動的に保存される問題を解決するにはどうすればよいですか? 1. キャッシュをクリアする まず、Xiaohongshu のキャッシュ データをクリアしてみます。手順は次のとおりです: (1) 小紅書を開いて右下隅の「マイ」ボタンをクリックします。 (2) 個人センター ページで「設定」を見つけてクリックします。 (3) 下にスクロールして「」を見つけます。 「キャッシュをクリア」オプションを選択し、「OK」をクリックします。キャッシュをクリアした後、Xiaohongshu を再起動し、写真を投稿して、自動保存の問題が解決されるかどうかを確認します。 2. 小紅書バージョンを更新して、小紅書が正しく動作することを確認します。

コンピューターがドライバーを読み込めない場合、デバイスが正しく動作しないか、コンピューターと正しく対話できない可能性があることは誰もが知っています。では、このデバイスにドライバーをロードできないことを示すプロンプト ボックスがコンピューターに表示された場合、問題を解決するにはどうすればよいでしょうか?以下のエディタでは、問題を簡単に解決する 2 つの方法を説明します。このデバイスにドライバーをロードできません 解決策 1. スタート メニューで「カーネル分離」を検索します。 2. メモリの整合性をオフにします。上記のメッセージには、「メモリの整合性がオフになっています。デバイスは脆弱である可能性があります。」というメッセージが表示されます。戻るボタンをクリックして無視してください。使用には影響しません。 3. マシンを再起動すると、問題が解決することがあります。

1. 今すぐ Toutiao の記事を公開してどうやってお金を稼ぐことができますか?今すぐ Toutiao で記事を公開して収入を増やす方法! 1. 基本的な権利と利益の有効化: オリジナルの記事は広告によって利益を得ることができますが、利益を得るにはビデオが横画面モードでオリジナルである必要があります。 2. ファン100人の権利を有効化:ファン数が100人以上に達すると、マイクロヘッドライン、オリジナルQ&A作成、Q&Aから利益を得ることができます。 3. オリジナル作品にこだわる: オリジナル作品には記事、小見出し、質問などが含まれ、300 ワード以上であることが求められます。違法に盗用された作品をオリジナル作品として出版した場合、クレジットポイントが減点され、利益も差し引かれますのでご注意ください。 4. 垂直性:専門分野の記事を書く場合、分野を超えて自由に記事を書くことができず、適切な推薦が得られず、専門性や洗練度が得られず、ファンもつきにくいそして読者たち。 5. 活動: 高活動、

タイトル: Oracle エラー 3114 の分析: 原因と解決策 Oracle データベースを使用すると、さまざまなエラー コードが頻繁に発生しますが、その中で比較的一般的なのはエラー 3114 です。このエラーには通常、データベース リンクの問題が関係しており、データベースへのアクセス時に例外が発生する可能性があります。この記事では、Oracle エラー 3114 を解釈し、その原因について説明し、エラーを解決するための具体的な方法と関連するコード例を示します。 1. エラー 3114 の定義 Oracle エラー 3114 パス

WordPress バックエンドのコードの文字化けが心配ですか?これらの解決策を試してください。具体的なコード例が必要です。Web サイト構築で WordPress が広く使用されるようになったことで、多くのユーザーが WordPress バックエンドでコードが文字化けする問題に遭遇する可能性があります。このような問題が発生すると、バックグラウンドの管理インターフェースが文字化けして表示され、ユーザーに多大な迷惑をかけてしまいます。この記事では、WordPress バックエンドでの文字化けのトラブルを解決するための一般的な解決策をいくつか紹介します。 wp-config.php ファイルを変更し、wp-config を開きます。

タイトル: 中国語データを Oracle にインポートする際の文字化けの問題を解決する方法とコード例。中国語データを Oracle データベースにインポートすると、文字化けが頻繁に発生します。これは、データベースの文字セット設定が間違っているか、インポート中のエンコード変換の問題が原因である可能性があります。プロセス。 。この問題を解決するには、インポートされた中国語データが正しく表示されるようにするためのいくつかの方法を講じることができます。以下に、いくつかの解決策と具体的なコード例を示します。 1. データベースの文字セット設定を確認します。 Oracle データベースでは、文字セット設定は次のとおりです。
