0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

XMLSerializerで後方互換を考慮したかった

Posted at

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>

NameIdプロパティを削除し、NewPropertyを追加した形になります。
これを読み込ませると、Name = nullId = 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の種類の追加を考慮して、文字列でシリアライズ、デシリアライズできるようにしよう

というわけで、この辺の設計はきちんと後々のことまで考えてするようにしましょうね!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?