⚙️

XmlSerializer と DefaultValueAttribute は一緒に使うとよくないことが起きるらしい

に公開

DefaultValueAttribute 属性が付けられたフィールドを XmlSerializer クラスでシリアライズすると、XML に値が書き込まれません。また、デシリアライズ時にも何もしてくれないため、データが消えてしまいます。再現するコードを以下に示します。

public class Program
{

    // DefaultValue を付けたメンバーを持つクラス
    public class Hoge
    {

        [DefaultValue(100)]
        public int Piyo { get; set; }

    }

    private static void Main()
    {
        var hoge = new Hoge();
        var serializer = new XmlSerializer(typeof(Hoge));
        // DefaultValue と同じ値を設定します
        hoge.Piyo = 100;
        using (var stream = File.Open("hoge.xml", FileMode.Create, FileAccess.ReadWrite))
        {
            // シリアライズします
            serializer.Serialize(stream, hoge);
            // ストリームの位置を先頭に戻します
            stream.Position = 0;
            // デシリアライズします
            hoge = (Hoge)serializer.Deserialize(stream);
        }
        // 結果を出力します
        Console.WriteLine("piyo: " + hoge.Piyo);
        Console.ReadLine();
    }

}

このコードを実行すると、次のような結果になります。

piyo: 0

見やすさのために改行を入れていますが出力される XML は以下のとおりです。確かに値が出力されていません。

<?xml version="1.0"?>
<Hoge xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

この動作は仕様であるようです。

ただし、このままでは期待した動作になりません。そのため、シリアライズ対象のクラスのコンストラクターで DefaultValueAttribute 属性の値によって初期化するようにします。

public Hoge()
{
    Array.ForEach(this.GetType().GetProperties(), x =>
    {
        var attr = Attribute.GetCustomAttribute(x, typeof(DefaultValueAttribute));
        if (attr != null)
        {
            x.SetValue(this, ((DefaultValueAttribute)attr).Value, null);
        }
    });
}

この修正を加えて実行すると、結果は次のようになります。

    piyo: 100

注意点を以下に示します。

  • リフレクションを使用するため、パフォーマンスに影響を及ぼす可能性がある
  • インスタンスの初期化時には常に DefaultValueAttribute 属性の値で初期化される

Discussion