⚙️
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