Queue<T>をXMLでシリアライズした時に、そのままだと例外が発生したためその調査と対策を記述。
実装コード
/// シリアル化クラス
public class Hage
{
public Queue<long> LongBuffer { get; set; } = new Queue<long>();
}
まずは、上記のようなコードを以下のようなコードでシリアル化した。
static void WriteFile2(string filename)
{
Hage buffer = new Hage();
buffer.LongBuffer.Enqueue(1);
buffer.LongBuffer.Enqueue(2);
switch (Path.GetExtension(filename)) {
case ".json":
var json = new DataContractJsonSerializer(typeof(Hage));
using (var stream = File.Create(filename)) {
json.WriteObject(stream, buffer);
}
break;
case ".xml":
var xml = new XmlSerializer(typeof(Hage));
using (var stream = new StreamWriter(filename, false, Encoding.UTF8)) {
xml.Serialize(stream, buffer);
stream.Flush();
}
break;
}
}
今回、json/xmlでそれぞれどのように変わるかも調べたため、拡張子によりjson/xmlを切り替えられるようにした。
XMLで出力
出力エラーの発生
上のコードでXMLでデータを出力した結果、以下のような例外が発生した。
InvalidOperationException: You must implement a default accessor on System.Collections.Generic.Queue`1[[System.Int64, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] because it inherits from ICollection.
これに関してググったところ、それほど多くはないが結構困っている人がいるようだった。
日本語にすると、「ICollectionを継承しているのデフォルトアクセサがない」とのこと。
XMLで出力する場合の条件
XMLで出力する場合、以下のような条件があるとのこと。
個々の内容と、今回出た例外のメッセージから、以下の条件が満たされていないためだと思われる。
Enumerable の他に ICollection も実装するクラス (CollectionBase など) は、整数を受け取るパブリックなインデックス付き Item プロパティ (C# の場合はインデクサー) と、integer 型のパブリックな Count プロパティを持つ必要があります。 Add メソッドに渡されるパラメーターは、Item プロパティから返された型と同じ型か、またはその型の基本型の 1 つである必要があります。
つまり、[]アクセッサとAddメソッドが必要だったらしい。
対応方法
結果的には、以下のように、Queue<T>を継承したXML出力可能なQueueを作成することで対応ができた。
public class XmlQueue<T> : Queue<T>
{
public T this[int index] {
get {
int count = 0;
foreach (T o in this) {
if (count == index)
return o;
count++;
}
return default(T);
}
}
public void Add(T item)
{
base.Enqueue(item);
}
}
出力結果
対応した方法でXMLを出力した場合、以下のような出力結果となった。
<?xml version="1.0" encoding="utf-8"?>
<Hage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LongBuffer>
<long>1</long>
<long>2</long>
</LongBuffer>
</Hage>
出力フォーマットとしては、List<T>と同じような内容となっていた。
JSONで出力
JSONで出力する場合には、Queue<T>のままそのまま出力は可能だった。
ただ、XMLで出力可能なクラスをそのまま流用したかったため、上の対応を起こった際の各クラスの記述で、どのようにJSON出力が変わるかも調査してみた。
Queue<T>そのまま出力
/// シリアル化クラス
public class Hage
{
public Queue<long> LongBuffer { get; set; } = new Queue<long>();
}
上記を出力した場合、以下のような出力となった。
{"LongBuffer":{"_array":[1,2,0,0],"_head":0,"_size":2,"_tail":2,"_version":3}}
Queueu<T>の実装を見たところ、クラスにSerializable属性が付いていたため、内部で保持しているprivateなメンバーが出力されたものと思われる。
XMLと共用化したクラスで出力
/// シリアル化クラス
public class Hage
{
public XmlQueue<long> LongBuffer { get; set; } = new XmlQueue<long>();
}
上で作成したXmlQueue<T>を使った場合。
{"LongBuffer":[1,2]}
デフォルトアクセサが使われたためか、Queue<T>とは異なりList<T>と同じ出力結果となった。
DataContractを付けた場合
XmlQueue<T>にDataContract属性を付けた場合は、以下の様になった。
{"LongBuffer":{"_array":[1,2,0,0],"_head":0,"_size":2,"_tail":2,"_version":3}}
これは、queue<T>の実装と同じ出力内容。
Serializableを付けた場合
XmlQueue<T>にSerializable属性を付けた場合は、以下の様になった。
{"LongBuffer":[1,2]}
結論
結論的に言うと、Queue<T>とXmlQueue<T>にDataContract属性を付けた場合が同じ結果となり、XmlQueue<T>そのままもしくはSerializable属性を付けた場合、同じ出力結果となった。
コメント