シリアライズでファイル保存/読込が何倍も楽になる!

2023年2月7日

こんにちは、ノムノムです。

今回はシリアライズをやろうと思います。

これを使えば画面の情報をファイルに出力したり、読込んだりするのがとても楽になります。

シリアライズとデシリアライズ

シリアライズとは簡単にいうとオブジェクトをバイトに変換することです。

デシリアライズとはシリアライズの逆でバイトからオブジェクトに変換することです。

つまり以下の手順でファイルを保存します。

  1. 保存データを格納するクラスを作ります。
  2. インスタンス(実体)を作ります。(Newします。)
  3. インスタンスを作成したクラスの変数に保存するデータを代入します。
  4. 保存先ファイル名を取得してそのファイルの書き込み用のストリームを作成します。
  5. クラスの変数をファイル書き込み用のストリームにシリアライズして書き込みます。

読み込むときは以下の手順をします。

  1. 読み込むファイルをファイルオープンダイアログなどで取得します。
  2. 取得したファイルの読み込み用ストリームを作成します。
  3. それをクラス変数にデシリアライズして復元します。
  4. 復元したクラス変数を取得します。

ところどころ読んでも意味の分からないところもあると思いますが、安心してください。ちゃんと説明します。

シリアライズでファイルを保存/読込する

それでは順を追って説明していきます。

1.保存データを格納するクラスを作成します。

まずはシリアライズするためのクラスを作成してください。

次にシリアライズを使うための準備としてソースの先頭にusingを使い、以下の行を追加します。

using System.Runtime.Serialization.Formatters.Binary;

次に「これはシリアライズするクラスですよ」という属性を追加する必要があります。クラス前に[Serializable]と追記して下さい

あとは格納するデータを宣言してあげます。
私の場合は、格納したり、呼び出したりするだけなのでプロパティにしちゃってます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;


namespace SelializeSample
{
    [Serializable]
    class MyDataObject
    {
        //エラー情報の格納
        public string ErrMsg { get; set; }
        
        //保存対象データを設定
        public bool Checked { get; set; }
        public string Text { get; set; }

        //保存
        public int Save(string file)
        {
            return 0;
        }

        //開く
        public int Load(string file)
        {
            return 0;
        }
    }
}

これで保存するデータを格納するクラスが完成しました。保存と読込をするので空の関数も用意しました。

2.実体の作成、値の代入

これは画面の方ですね。直接シリアライズとは関係ありませんのでサクッと終わらせます。
単純にダイアログを呼び出し、ファイル名を取得して保存なり、読込なりしています。


MyDataObject _data = new MyDataObject();

private void btnSave_Click(object sender, EventArgs e)
{
    string file = "";

    //保存ファイル名取得ダイアログ
    if (SaveFileDialog(ref file) == false) return;

    //保存データ取得
    _data.Checked = this.checkBox1.Checked;
    _data.Text = this.textBox1.Text;

    //データ保存
    _data.Save(file);

    //完了メッセージ
    MessageBox.Show("保存が完了しました。");
}

private void btnLoad_Click(object sender, EventArgs e)
{
    string file = "";

    //読込ファイル名取得ダイアログ
    if (OpenFileDialog(ref file) == false) return;

    //データ読込
    _data.Load(file);

    //読込データ取得
    this.checkBox1.Checked = _data.Checked;
    this.textBox1.Text = _data.Text;

    //完了メッセージ
    MessageBox.Show("読込が完了しました。");
}

private bool SaveFileDialog(ref string file_name)
{
    using (SaveFileDialog dlg = new SaveFileDialog())
    {
        dlg.FileName = file_name;                  //初期値なし
                                                   //初期フォルダ(マイドキュメント)
        dlg.InitialDirectory = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        dlg.Filter = "tstファイル(*.tst)|*.tst|すべてのファイル(*.*)|*.*";   
        dlg.FilterIndex = 1;                //Filterの1つめ画像ファイルを指定
        dlg.Title = "TSTファイル指定";     //タイトルを設定する
        dlg.RestoreDirectory = true;        //カレントディレクトリ復元

        //ダイアログを開く
        if (dlg.ShowDialog() != DialogResult.OK)
        {
            //OKが返ってこない場合は、キャンセルされたとする。
            file_name = "";
            return false;
        }

        //選択したファイル名を取得する
        file_name = dlg.FileName;
        return true;
    }
}

private bool OpenFileDialog(ref string file_name)
{
    using (OpenFileDialog dlg = new OpenFileDialog())
    {

        dlg.FileName = file_name;                  //初期値なし
                                                   //初期フォルダ(マイピクチャ)
        dlg.InitialDirectory = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        dlg.Filter = "tstファイル(*.tst)|*.tst|すべてのファイル(*.*)|*.*";
        dlg.FilterIndex = 1;                //Filterの1つめ画像ファイルを指定
        dlg.Title = "TSTファイル指定";     //タイトルを設定する
        dlg.RestoreDirectory = true;        //カレントディレクトリ復元
        dlg.Multiselect = false;            //複数選択可否

        //ダイアログを開く
        if (dlg.ShowDialog() != DialogResult.OK)
        {
            //OKが返ってこない場合は、キャンセルされたとする。
            file_name = "";
            return false;
        }

        //選択したファイル名を取得する
        file_name = dlg.FileName;
        return true;
    }
}

保存ファイルの拡張子をTSTにしました。別に意味はありません。適当です。

3.シリアライズでファイル保存

それでは本題シリアライズしてファイルに入れましょう。

ソースを見た方が理解が早いでしょう

//保存
public int Save(string file)
{
    //シリアライズ
    using (System.IO.Stream stream = System.IO.File.OpenWrite(file))
    {
        BinaryFormatter formatter = new BinaryFormatter();

        formatter.Serialize(stream, this);

        stream.Close();
    }

    return 0;
}

解説をします。

  1. 出力用のストリーム(stream)を作成します。(5行目)
  2. BinaryFormatterクラスでオブジェクト(this)をストリームにシリアライズして書き込みます。(9行目)
  3. ストリームを閉じます。(11行目)

意外とあっさり終わりました。

デシリアライズでファイル読込

次はデシリアライズでファイルを読込みます。これはシリアライズの逆をやればいいですね。

//開く
public int Load(string file)
{
    //デシリアライズ
    using (System.IO.Stream stream = System.IO.File.OpenRead(file))
    {
        BinaryFormatter formatter = new BinaryFormatter();

        MyDataObject obj = (MyDataObject)formatter.Deserialize(stream);

        //取得したデータを自身に渡す。
        this.Checked = obj.Checked;
        this.Text = obj.Text;

        stream.Close();
    }

    return 0;
}

解説します。

  1. ファイル読込ストリームを取得します。(5行目)
  2. BinaryFormatterクラスでデシリアライズします。(9行目)
  3. 戻り値がobjectなのでキャストして型変換してあげます。 (9行目)
  4. 取得したデータをデータに戻します。 (12~13行目)
  5. ストリームを閉じます。 (15行目)

ちなみに自身に直接代入することはできませんでした。残念。

シリアライズ保存読込サンプルクラス

それではここまでの内容を踏まえてクラスを完成させてみたいと思います。
できればもっと使いやすい方がいいかと思いいろいろやってみました。

DataClassの中身を変えるだけでいろいろ対応できるようにしてみました。

class MyDataObject
{
    //エラー情報の格納
    public string ErrMsg { get; set; }

    [Serializable]
    public class DataClass
    {
        //保存対象データを設定
        public bool Checked { get; set; }
        public string Text { get; set; }

    }

    //ファイル保存用データクラス
    public DataClass Data = new DataClass();

    //保存
    public int Save(string file)
    {
        try
        {
            //シリアライズ
            using (System.IO.Stream stream = System.IO.File.OpenWrite(file))
            {
                BinaryFormatter formatter = new BinaryFormatter();

                formatter.Serialize(stream, Data);

                stream.Close();
            }
        }
        catch (Exception ex)
        {
            ErrMsg = ex.Message;
            return -1;
        }

        return 0;
    }

    //開く
    public int Load(string file)
    {

        //ファイル存在チェック
        if (System.IO.File.Exists(file) == false)
        {
            return -9;
        }

        try
        {
            //デシリアライズ
            using (System.IO.Stream stream = System.IO.File.OpenRead(file))
            {
                BinaryFormatter formatter = new BinaryFormatter();

                //データの取得
                Data = (DataClass)formatter.Deserialize(stream);

                stream.Close();

            }
        }
        catch (Exception ex)
        {
            ErrMsg = ex.Message;
            return -1;
        }

        return 0;
    }
}

まとめ

データ保存や読込ができるとプログラミングの幅が広がりますね。
今回のシリアライズは暗号化などしていないので、そのままテキストファイルで読み込むとある程度の内容が普通に書かれています。

もしパスワードなどを扱う場合や、他人に中身を知られたくない場合は暗号化も合わせてやってみる方がよいでしょう。

ここまで読んで頂いてありがとうございました。