1. Введение
プログラムでオブジェクトを作るのにはもう慣れてるよね。これからクラスとオブジェクトをもっと深く学ぶけど、今でも変数やリスト、単純な文字列ってただのデータじゃなくて、プログラム内の「実体」なんだ。例えば変数を作れるよね: int age = 30; とか string name = "ヴァーシャ";。 でも、名前、年齢、住所、お気に入りの本のリストなどを持つ「ユーザー」全体の情報をファイルに保存したいとしたらどうする?
想像してみて:ゲームを作っているとしよう。Playerというオブジェクトがあって、体力、レベル、インベントリ(アイテムのリスト)、マップ上の座標など色々持ってる。プレイヤーは遊んでレベルアップして、かっこいいアーティファクトを見つける。そして突然ゲームを終了する。どうなる?メモリにあった冒険のデータは消えちゃう!悲しい!これを防ぐために、オブジェクトPlayerの状態をファイルに保存して、プレイヤーが戻ったらそれを読み込む必要があるんだ。
そこで登場するのがシリアライズだ。メモリ上の「生きている」オブジェクトと、ディスク上の「死んでるけど永続的な」データの間に橋をかける役割を果たすんだよ。
2. Процесс сериализации (от объекта к файлу)
オブジェクトをファイルに書き込めるバイト列に変えるこの「魔法」がどう動くか見てみよう。
例えば、こんなクラス Book(本)があるとする):
// これはBookオブジェクトを作るための「設計図」だよ
public class Book
{
// 本のプロパティ
public string Title { get; set; } // 書名
public string Author { get; set; } // 著者
public int Year { get; set; } // 発行年
// コンストラクタ - 新しいBookオブジェクトを作る特別なメソッド
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
// 本の情報を見やすく出力するメソッド(シリアライズには必須じゃないけど便利)
public void DisplayInfo()
{
Console.WriteLine($"書名: {Title}, 著者: {Author}, 年: {Year}");
}
}
ステップ1: シリアライズするオブジェクトを作る。
まずは保存したいオブジェクトが必要だよね。例えば、Bookのインスタンスを作ったとしよう:
Book myFavoriteBook = new Book("銀河ヒッチハイク・ガイド", "ダグラス・アダムズ", 1979);
このオブジェクト myFavoriteBook は今メモリにある状態だよ。
ステップ2: ツール(シリアライザ)の選択。
オブジェクトをそのままディスクに「置く」ことはできない。コンピュータはオブジェクトをファイルで直接理解しないから、バイトが必要なんだ。そこで必要なのが専用のツール、つまりシリアライザ。このツールはオブジェクトを分解して、そのプロパティ(Title, Author, Year)を取り出して、バイト列やテキスト(例えばJSONやXML)に変換してくれる。
今日は具体的な実装には深入りしないけど、特別な「変換ボックス」だと考えておけばOKだよ。
ステップ3: オブジェクトをデータのストリームに変換する。
シリアライザは myFavoriteBook を受け取って、そのプロパティ(Title, Author, Year)を順に取り出して、ファイルに書ける形式に変換する。これらのバイト(またはテキスト)は一つのデータストリーム、長い「テープ」にまとめられる。
ステップ4: ストリームをファイルに書き込む。
こうして出来た「テープ」を、FileStream や必要なら StreamWriter(テキスト形式、例えばJSONを選んだ場合)や、純粋なバイナリならFileStreamだけを使ってディスクに書き込むんだ。
3. Процесс десериализации (от файла к объекту)
ステップ1: ファイルからデータストリームを読む。
また FileStream やテキストの場合は StreamReader を使ってファイルの中身を読む。データはバイトの「テープ」かテキストとして返ってくるよ。
ステップ2: ツール(デシリアライザ)の選択。
逆方向のツール、つまりデシリアライザが必要だ。受け取ったバイトやテキストをどう解釈してオブジェクトの構造に戻すかを知っている必要がある。重要なのは:通常、シリアライズに使ったのと同じタイプ(同じライブラリ)を使ってデシリアライズするということ。さもないと「組み立て方」を間違えてしまう。
ステップ3: ストリームをオブジェクトに戻す。
デシリアライザはデータを読み取って、まず Title がどこにあるかを理解し、次に Author、その次に Year を見つけて、そこから新しい Book オブジェクトをメモリ上に作ってプロパティを埋めるんだ。
ステップ4: 完成したオブジェクトの取得。
できた!メモリ上にまたちゃんと操作できる Book オブジェクトが戻ってきたよ。
Пример: Сохраняем нашу "Супер-Книгу" «вручную» (для понимания концепции)
まずは専用ライブラリを使わずに、StreamWriter と StreamReader を使った最も単純な「手動シリアライズ/デシリアライズ」をやってみよう。仕組みを理解するのに役立つよ。
私たちのオブジェクト Book:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
public void DisplayInfo()
{
Console.WriteLine($"書名: \"{Title}\", 著者: {Author}, 年: {Year}");
}
}
手動シリアライズ: メソッド SaveBookToTextFile
本のプロパティをテキストファイルに1行ずつ保存するメソッドを作ろう。
void SaveBookToTextFile(Book book, string filePath)
{
using var writer = new StreamWriter(filePath);
writer.WriteLine(book.Title);
writer.WriteLine(book.Author);
writer.WriteLine(book.Year);
}
何をしているかというと、StreamWriter を開いて Title、Author、Year を順番に書き込んでいるだけ。これが最も単純なシリアライズのスキーマだよ。
このコードを実行するとファイルの中身はこんな感じになる:
銀河ヒッチハイク・ガイド
ダグラス・アダムズ
1979
手動デシリアライズ: メソッド LoadBookFromTextFile
ファイルを読み込んで新しい Book オブジェクトを組み立てるメソッドを書こう。
Book LoadBookFromTextFile(string filePath)
{
using var reader = new StreamReader(filePath);
string title = reader.ReadLine();
string author = reader.ReadLine();
int year = int.Parse(reader.ReadLine());
return new Book(title, author, year);
}
そしてこれらのメソッドを Main で使う例:
// オブジェクトを作る
var myBook = new Book("銀河ヒッチハイク・ガイド", "ダグラス・アダムズ", 1979);
string filePath = "my_favorite_book.txt";
// ファイルに保存する
SaveBookToTextFile(myBook, filePath);
// ファイルから読み込む
Book loadedBook = LoadBookFromTextFile(filePath);
LoadBookFromTextFile の中で何が起きているか?StreamReader を開いて同じ順序で行を読み取る:まずタイトル、次に著者、最後に年を int.Parse で変換している。それから新しい Book のインスタンスを作るんだ。
実用的には File.Exists をチェックしたり try-catch でエラー処理を入れたりするべきだけど、ここではアイデアに集中しよう。
Почему «ручная» сериализация — это плохо (и почему нужны библиотеки)?
- コードが手作業だらけ。 各プロパティを自分で書いて、後で読み取る必要がある。オブジェクトやフィールドが増えたらコードがすぐ膨らむ。
- 変更に弱い。 新しいプロパティ Pages (int) を追加したら?書き込みと読み込みの両方を直さないといけないし、順序も厳密に管理する必要がある。
- 複雑な構造には向かない。 ネストしたコレクションやオブジェクト(例えば List<Chapter>)があるとコードがスパゲッティになる。
- フォーマットと効率。 テキスト形式はシンプルだけど常にコンパクトで安全とは限らない。バイナリデータなら手動でバイトを扱う必要があって、BinaryWriter/BinaryReader などを使うことになる。
- メタデータがない。 ファイルは「最初の行が Title、3行目が Year」だとは知らない。専用のシリアライザはメタデータを残したり、モデルの変更に強くしたりできる。
だから実際のプロジェクトでは、オブジェクトの分解・再構築を自動でやってくれる既成のシリアライザライブラリ(JSON、XML、バイナリ形式に対応するもの)を使うんだ。これについては次の講義でやるよ!
Практическое применение: зачем нужна сериализация?
- データの保存と読み込み。 設定、ゲームの状態、構成情報。
- ネットワーク経由でのデータ送受信。 サービス間で複雑なオブジェクトをやり取りする(多くの場合 JSON)。
- キャッシュ。 一度取得したデータを高速に再利用するため。
- 複雑なオブジェクトのログ出力。 デバッグや監査のために便利。
- ディープコピー。 シリアライズ+デシリアライズでオブジェクトグラフをクローンする方法として使える。
シリアライズは現代のソフトウェアの重要な基礎だよ:ゲームの進行状況の保存からウェブサービスとの連携まで、至る所で使われているんだ。
GO TO FULL VERSION