C# Queue<T>のシリアライズ

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で出力する場合、以下のような条件があるとのこと。

XML シリアル化の詳細 - .NET
シリアル化は転送できる形式にオブジェクトを変換します。 この記事では、XML シリアル化と XmlSerializer クラスの概要について説明します。

個々の内容と、今回出た例外のメッセージから、以下の条件が満たされていないためだと思われる。

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属性を付けた場合、同じ出力結果となった。

コメント

タイトルとURLをコピーしました