読者です 読者をやめる 読者になる 読者になる

からめもぶろぐ。

ワタシ SharePoint チョット デキル

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

C#

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

どうもこの動作は仕様らしいとのこと。

https://support.microsoft.com/ja-jp/kb/325691

リンク先には、

マイクロソフトは、.NET Framework の次のメジャー バージョン リリースでこの動作を変更する予定です。

とありますが、.NET 4 でも修正されていないので今後も修正されないんだろうと思います。*1 とはいえ、それではあまりにひどいのでちょこっと手を入れて、シリアライズする対象のクラスのコンストラクタに次のようなコードを書き加えてみます。自前で 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);
        }
    });
}

これで実行すると、結果は以下のようになります。*2

piyo: 100

リフレクション使うのでパフォーマンスが多少気になるのと、シリアライズ関係なく DefaultValueAttribute の値で初期化されてしまいますが、とりあえず困ったことはないので大丈夫かと思います。

*1:リンク先の最終更新日が 2007 年なのでその時点の最新版は .NET 2.0 かと思います。

*2:出力される XML は同じです。