XMLでのGuidのSerialize/Deserializeまとめ
2017/06/02: ヘルパークラスを利用した実装を追記。
System.Guidとは?
System.Guid構造体は、128ビット (16バイト)の値が衝突されないと期待されるGUIDとか、UUIDとか言われる値を生成・管理するValueType。
一般には、32桁の16進数で表現される。
128ビットだから衝突しない訳ではなくて、例えば、ファイルのGUIDをバイナリデータが完全に一致するときに再現性良く生成したいから言って、ファイルのMD5ハッシュ値をこの構造体に突っ込んでも、それは"GUIDっぽい何か"でしかないので注意。
wikiの記事によると、
なお、GUIDの内部表現はバイト配列で、書式によってDWORD、WORD、BYTE配列に分割するが、DWORDやWORD表現を使用する場合の表記はビッグエンディアンを採用することが推奨されているので、Guidのコンストラクタにハッシュ値のバイト配列を渡すと、バイトオーダーがひっくり返ったように見える。
標準Serialize/Deserialize処理
XML Schema Definitionでは、標準でGUIDを型として定義していないが、標準のXmlSerializerでは属性/要素ともに、GuidのSerialize/Deserializeに対応している。
標準では、xsd:stringにマップされるが、マイクロソフトの各種フレームワークでは、スキーマ定義を文字列の制限による派生によりsimpleTypeとして個別に定義されているので、スキーマ定義する場合はコピーして定義しなおすと良い。
GUID simple Type [OneNote 2003 XML Schema Reference]
一般に、採用されている出力フォーマットは、括弧なしのハイフン区切りの32桁の16進数で書式文字列が"D"のパターン。
Guid.ToString メソッド (String) (System)
ただし、入力フォーマットは、他のすべての書式を受け入れる。
Guid.Parse メソッド (String) (System)
この辺は、XmlConvertクラスを使っても事情は同じである。
敢えて、書式"D"に制限するには、スキーマ検証で行うか、文字列で処理して、書式"D"限定でParseする。
標準パターン
Guidに限らず、試しそうなパターンをLinqPadのスクリプトで書いてみた。
- test/@guid (Test.GuidA), test/Guid (Test.Guid)
- Guidの標準Serialize/Deserialize (ValueType)
- Guid.Emptyが"00000000-0000-0000-0000-000000000000"と出力されてしまい、如何にも「初期化し忘れました」と白状している様でマヌケ
Nullableを使用するパターン
- test/@nullableGuid (Test.NullableGuidA), test/NullableGuid (Test.NullableGuid)
- XmlSerializerはnullのオブジェクトを出力しないのでNullable
を試行 - サポートされているxsd:simpleType以外はxsd:complexTypeとみなされるので、属性に割り当てられない
- 要素は可能だが、nullだとxsi:nil=“true"が必要でやっぱりちょっとマヌケ
- Nullable
では、XmlElementAttribute.IsNullable = true固定で省略可能 - ValueTypeにXmlElementAttribute.IsNullable = trueを付けると、Nullable
を使えと怒られる - XmlElementAttribute.IsNullable = trueは、null値を持つ型に対して、xsi:nil=“true"を付けて要素を出力するためのオプション
- Nullable
- XmlSerializerはnullのオブジェクトを出力しないのでNullable
文字列を使用するパターン
- test/@guidString (Test.GuidStringA), test/GuidString (Test.GuidString)
- XMLでxs:stringはすべての単純型 (xsd:simpleType)の先祖。いわばobject型
- 困ったとき(属性や要素に型を割り当てられないとき)はxs:stringにする
- 属性や子要素を持たずにText()をプロパティとして持つクラスを属性に割り当てるとき
- 属性ではT[]にバインドされるxs:list (空白区切りリスト)をText()にバインドしたいとき (標準ではTを要素名とするマヌケなコレクションになる)
- 拡張メソッドやラッピングクラスを作ると便利
- 自動実装プロパティが使用できないのが残念だが、文字列パース時にカスタム処理が必要な場合はお勧め
XmlSerializerの特殊プロパティサポート機能を使用するパターン
- test/@attributableGuid (Test.AttributableGuidA), test/AttributableGuid (Test.AttributableGuid)
- XmlSerializerの4つのマジックの一つ
- suffixにSpecifiedを付けたbooleanのプロパティを作成してXmlIgnoreAttributeでマークすると、trueの時だけ対応するプロパティを出力する
XmlSerializerの特殊メソッドサポート機能を使用するパターン
- test/@handledGuid (Test.HandledGuidA), test/HandledGuid (Test.HandledGuid)
- XmlSerializerの4つのマジックの一つ
- prefixにShouldSerializeを付けたbooleanを返すメソッドを実装すると、trueの時だけ対応するプロパティを出力する
ヘルパークラスXmlGuidを使用するパターン (2017/06/02)
- test/@guidWrapper (Test.GuidWrapperA), test/GuidWrapper (Test.GuidWrapper), test/CastedGuid (test.CastedGuid)
- ヘルパークラスのXmlGuidを使用するパターン
- Guid, Guid?とのキャスト演算子を定義したクラスの応用
- 属性は、string型を経由する常套手段だが、文字列とのキャスト演算子を定義しているので、プロパティの実装は簡潔になっている
- 要素は、そのまま宣言しても良いし、Guid?で宣言してXmlElementにtypeofで型を設定しても良い
- プロパティの型をstringやGuidにすると、primitive型はダメと言われる
- https://stackoverflow.com/questions/637933/how-to-serialize-a-timespan-to-xml
- ヘルパークラスのXmlGuidを使用するパターン
LinqPadスクリプト
デフォルト値の出力
<?xml version="1.0" encoding="utf-8"?> <test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" guid="00000000-0000-0000-0000-000000000000"> <Guid>00000000-0000-0000-0000-000000000000</Guid> <NullableGuid xsi:nil="true" /> </test>
値を持つ場合の出力
<?xml version="1.0" encoding="utf-8"?> <test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" guid="9aa7bd7a-56f7-429b-b0c7-b261be4e376e" guidString="feea321a-7a5c-4e3a-bf09-e699f3a04df2" attributableGuid="676a0a57-6fe9-4024-9e0e-b685e4d983b5" handledGuid="3c6ae7b8-45ef-4869-a871-82cbf7714f47" guidWrapper="fe49637f-8437-4657-9927-1173e6b7dd6c"> <Guid>03835418-7ec2-4cab-8bab-0d9668b64b45</Guid> <NullableGuid>45ede748-7c7b-4bc5-9c73-c165950d65dc</NullableGuid> <GuidString>94985332-67ad-43a0-86e7-aba5d6278c23</GuidString> <AttributableGuid>f127c2e8-c8d5-4ba5-a721-52db711f205c</AttributableGuid> <HandledGuid>e01b34e4-85d8-4567-bf6e-33686ffedb63</HandledGuid> <GuidWrapper>0a744cb8-d16f-414a-b5e3-6faab6598bcb</GuidWrapper> <CastedGuid>6a6a80e1-d203-43f0-b620-f8a6745a8d4c</CastedGuid> </test>
残りの2つのマジックは?
- DefaultAttribute
- 対象のプロパティに付与する
- nullの場合にデフォルト値を割り当てる
- 割り当てる値がconst or staticの場合に使用可能
- Resetメソッド
- prefixにResetを付けたメソッドでプロパティにデフォルト値を割り当てる
- 動的な割り当てが可能