1. はじめに
多くの古いC#の教科書やサンプルで、クラスBinaryFormatterを使ったシリアライズを見かけます。かつては標準で、便利で速く、手間が少なかった。昔のサンプルはだいたいこんな感じでした:
// 危険な古いコード!新しいプロジェクトではやらないでください!
var bf = new BinaryFormatter();
using (var stream = File.Open("player.dat", FileMode.Create))
{
bf.Serialize(stream, playerObject);
}
しかし現代の.NET 9ではそのようなコードはもうコンパイルできません: BinaryFormatterはプラットフォームから削除されています。
英雄から厄介者への道
当初、BinaryFormatterは.NETオブジェクトのシリアライズ/デシリアライズの汎用メカニズムとして設計され、複雑なオブジェクトグラフを「そのまま」保存できました。時間が経つにつれてセキュリティと互換性の深刻な問題が明らかになり、今では過去のものになっています — ストリーミング時代のカセットテープみたいなものです。
2. BinaryFormatterを廃止した主な理由
巨大なセキュリティホール
BinaryFormatterはデータのシリアライズだけでなく、入力ストリームから型を復元することもできます。検証されていないデータ(例: ネットワーク経由で受け取ったもの)を無思慮にデシリアライズすると、攻撃者がペイロードを注入してリモートでコード実行(RCE)を引き起こす可能性があります。だからMicrosoftは長年にわたり繰り返し警告してきました: 「BinaryFormatterを使わないでください」。.NET 5からは廃止予定および危険としてマークされ([Obsolete])、.NET 9で削除されました。
クラスの内部構造への依存
BinaryFormatterのフォーマットは型の実装詳細(フィールド、パッキング、バージョン)に依存します。クラスにちょっとした変更(フィールドの追加/削除)が入るだけで、既に保存されたファイルとの互換性が壊れます。長期保存やバージョン間のデータ交換には向きません。
クロスプラットフォームの問題
BinaryFormatterで保存されたデータは.NETの内部表現に結び付いているため、別のプラットフォーム/言語や.NETの別バージョンで読み取るのは難しいことが多いです。
3. 「古いコード」をどう見つけて、どうするか
BinaryFormatterを使っている兆候はこんな感じです:
using System.Runtime.Serialization.Formatters.Binary; // <-- 怪しい!
BinaryFormatter bf = new BinaryFormatter(); // <-- 危険!
bf.Serialize(...);
bf.Deserialize(...);
現代の.NETではこのコードはコンパイルしません: ツールが「型または名前が見つかりません」というエラーを出します。レガシーな .NET Framework プロジェクトでは最低でも強い警告が出ます。
もしこのようなコードを見つけたら、BinaryFormatterを現代的で安全な代替に置き換えてください。
今日使うべきフォーマットとクラス
JSON用
- System.Text.Json — 速くて安全、.NETに組み込み。 公式ドキュメント
- Newtonsoft.Json — 複雑なケースで人気のサードパーティライブラリ。
XML用
- XmlSerializer — XMLへのシリアライズ。 ドキュメント
バイナリフォーマット用
- System.Formats.Cbor (CBOR)
- Protobuf.Net (Protocol Buffers), MessagePack for C#
特殊な要件があるなら専用のフォーマット/ライブラリを選んでください — BinaryFormatterのような「万能」なバイナリシリアライザはもう存在しませんし、それで良いんです。
4. BinaryFormatterからの書き換え
何に使ってはいけないか
やってはいけないこと:
- ユーザー設定や重要なデータの保存に使うこと。
- ネットワーク経由でデータを送受信すること(クライアント ↔ サーバー)。
- 検証されていないソースからのデータをデシリアライズすること。
代わりに:
- 日常的な用途には — System.Text.Json。
- 設定ファイルには — JSON、XML 等。
- 言語間の交換には — クロスプラットフォーム互換のフォーマット(JSON、XML、Protobuf、MessagePack)。
今はどんなコードを書くべきか
JSONへのシリアライズ(推奨)
using System.Text.Json;
// あなたのクラス:
public class Player
{
public string Name { get; set; } = "";
public int Health { get; set; }
public bool IsAlive { get; set; }
}
// シリアライズするオブジェクト
var player = new Player { Name = "アラゴルン", Health = 100, IsAlive = true };
// オブジェクトをJSON文字列に変換
string json = JsonSerializer.Serialize(player);
// ファイルに保存 — 安全!
File.WriteAllText("player.json", json);
// ファイルからオブジェクトを復元する:
string loadedJson = File.ReadAllText("player.json");
Player loadedPlayer = JsonSerializer.Deserialize<Player>(loadedJson)!;
XMLへのシリアライズ(厳格なフォーマットが必要な場合)
using System.Xml.Serialization;
// クラスはパラメータ無しのpublicコンストラクタを持つ必要があります
var player = new Player { Name = "アラゴルン", Health = 100, IsAlive = true };
var serializer = new XmlSerializer(typeof(Player));
using (var fs = File.Create("player.xml"))
{
serializer.Serialize(fs, player);
}
視覚的な図: 旧式 vs 新式
| タスク | 古いやり方 (BinaryFormatter) | 現代のやり方 |
|---|---|---|
| ファイルへのシリアライズ | |
|
| ファイルからのデシリアライズ | |
|
| セキュリティ | 攻撃に脆弱 (RCE) | 安全なパーサーと厳密なモデル |
| クロスプラットフォーム | いいえ | はい (JSON/XML/Protobuf) |
| 速度 | 速いが危険 | 速くて安全 |
5. XmlSerializerを使うときの典型的なミス
ミス #1: privateなプロパティやフィールド。XmlSerializerはpublicなプロパティだけをシリアライズします; protectedでもエラーになることがあります。
ミス #2: デフォルトコンストラクタがない。パラメータ無しのpublicコンストラクタが必要で、なければシリアライズ/デシリアライズが動きません。
ミス #3: データ型の不一致。XmlSerializerはDictionary、interface、delegateでうまく動作しないことが多い — ラッパーを使うか別のフォーマットを検討してください。
ミス #4: 循環参照 (circular reference)。XmlSerializerは A → B → A のようなグラフをサポートしません; そのようなケースでは適切な設定をしたJSONを使うことが多いです。
GO TO FULL VERSION