⚙️

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

2022/01/01に公開

いまさらなのですが 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