XMLSerializer、使っていますか?
データの保存&読み込みが簡単にできるので重宝しています。
私も使っていたのですが、この度保存データ構造のほうに変更がありまして・・・
新しい保存データが古いアプリで読み込めないどころか、読み込まそうとするとクラッシュすることが判明してしまいました。
ちゃんと保存回りを作っておけばよかった!
ということで、過去の自分への戒めも込めてどうすればいいかまとめてみます。
この記事の目標
XMLSerializerを使って、データの保存を行います。
保存データの変更、つまりプロパティの追加や削除などが起きても、とりあえず読み込めるようにすることが目標です。
XMLSerializerの挙動
まず、挙動についてまとめます。
以下の2点の変更について、それぞれ影響を見ます。
- プロパティが追加 or 削除された
- 列挙子の種類が追加された
プロパティが追加 or 削除された場合
とりあえず例として以下の従業員クラスを考えてみましょう。
名前とIDだけがあるクラスです。
public class Employee
{
/// <summary>
/// 名前
/// </summary>
public string Name { get; set; }
/// <summary>
/// ID
/// </summary>
public int Id { get; set; }
}
これをXMLSerializerでXML化するとこうなります。
<Employee>
<Name>山田太郎</Name>
<Id>1</Id>
</Employee>
では、データの構造が変化したものとして、以下のXMLをデシリアライズしてみます。
<Employee>
<NewProperty>1</NewProperty>
</Employee>
Name
とId
プロパティを削除し、NewProperty
を追加した形になります。
これを読み込ませると、Name = null
、Id = 0
となります。
まとめると、
- プロパティが追加された場合
- 追加プロパティは無視される → 対策不要
- プロパティが削除された場合
- 型ごとのデフォルトの値が使われる →
NullReferenceException
が発生しうる
- 型ごとのデフォルトの値が使われる →
nullチェックをするか、初期値をちゃんと入れておけば問題なく読み込めます。
初期値を入れる場合はデシリアライズする際、引数のないコンストラクタが呼び出されるので、そこで設定します。
列挙子の種類が追加された場合
またもや従業員クラスを例にしてみます。
今度は役職だけ持っているクラスになります。
public class Employee
{
/// <summary>
/// 役職
/// </summary>
public PostType PostType { get; set; }
}
public enum PostType
{
/// <summary>
/// 部長
/// </summary>
Chief,
/// <summary>
/// 一般
/// </summary>
Staff,
}
これをXMLSerializerでXML化するとこうなります。
<Employee>
<PostType>Chief</PostType>
</Employee>
列挙子の種類が追加したものとして、以下のXMLをデシリアライズしてみます。
<Employee>
<PostType>NewValue</PostType>
</Employee>
デシリアライズすると、InvalidOperationException
例外が発生します。
これは、PostType
列挙子にNewValue
という値が存在しないため、
文字列をPostType
列挙子に変換できないために発生します。
直接列挙子をシリアライズするのではなく、文字列や数値に変換してからシリアライズすることで対応できそうです。
/// <summary>
/// 役職
/// </summary>
[XmlIgnore]
public PostType PostType { get; set; }
public string PostString
{
get
{
return PostType.ToString();
}
set
{
var result = PostType.Other;
if(Enum.TryParse<PostType>(value, out result))
{
PostType = result;
}
}
}
XmlIgnore
によってPostType
プロパティは直接シリアライズしないようにしています。
代わりに文字列型のPostString
プロパティを介してシリアライズします。
デシリアライズするときは、文字列をPostString
プロパティを介してPostType
プロパティに設定します。
これで文字列を変換できなかった場合の処理をこの中でできるようになっています。
まとめ
- プロパティの削除を見越してプロパティに初期値は設定しよう
- Enumの種類の追加を考慮して、文字列でシリアライズ、デシリアライズできるようにしよう
というわけで、この辺の設計はきちんと後々のことまで考えてするようにしましょうね!