※ この記事にはアフィリエイトリンクが含まれます
C#でObjectDisposedExceptionに悩んでいませんか?
- オブジェクトを使おうとしたら例外が出る。
- どのタイミングで破棄されたか分からない。
usingやDisposeの使い方がよく分からない。
結論から言うと、ObjectDisposedExceptionは「既に破棄されたオブジェクトを操作した」ことが原因です。
- 正しいライフサイクル管理(
usingやtry/finally)を行う。 - イベントや非同期処理で参照が残らないようにする。
Dispose後のアクセスを防ぐガードを入れる。
この記事ではC#のObjectDisposedExceptionの意味と発生原因、実践的な対処法を初心者向けに分かりやすく解説します。
この記事を読むと、再発防止の設計やデバッグ方法が分かり、実務でのトラブル対応が速くなります。
ObjectDisposedExceptionとは何か
ObjectDisposedExceptionは例外の一種です。
これは.NETのランタイムが、既にDisposeされたオブジェクトにアクセスしたときに投げます。
たとえばStreamやSqlConnectionなど、明示的に破棄する必要があるオブジェクトでよく見られます。
なぜこの例外が重要なのか
破棄されたリソースにアクセスすると予期せぬ動作やデータ破損につながります。
ObjectDisposedExceptionは開発者にその問題を知らせるための安全装置です。
発生する代表的なパターン
ここでは初心者がよく遭遇する典型的なケースを紹介します。
それぞれのケースで原因と対処を見ていきます。
1) usingブロックの外で参照している
usingはIDisposableを実装したオブジェクトをスコープ終了で破棄します。
using外でそのオブジェクトを使うとObjectDisposedExceptionになります。
// NGの例
using (var stream = File.OpenRead(path))
{
// 読み取り処理
}
// ここでstreamにアクセスすると例外になる
stream.Read(...);対処法はスコープの設計を見直すことです。
2) イベントハンドラが破棄後にも呼ばれる
オブジェクトAがイベントを登録し、Bが破棄された場合に発生します。
BがDisposeされてもAはイベント参照を保持していると、破棄済みのBにアクセスします。
// イベント解除を忘れる例
public class B : IDisposable
{
public void Dispose() { /* 破棄処理 */ }
}
// AがBのイベントを購読しているが解除しない対処はイベント購読の解除や弱参照の利用です。
3) 非同期処理で競合してDisposeされる
async/awaitの間にオブジェクトが破棄されると起こります。
たとえばUIスレッドでCancelしたり画面を閉じたりしたタイミングです。
async Task DoWorkAsync()
{
var client = new HttpClient();
var task = client.GetAsync(url);
// 他でclient.Dispose()が呼ばれる可能性がある
var res = await task; // ここで例外が発生することがある
}対処はDisposeのタイミングをawaitの後にするか、Disposeを管理する責任を明確にすることです。
原因の深掘りと設計上の考え方
ObjectDisposedExceptionは単なるバグ報告以上の意味があります。
それはオブジェクトの責任範囲とライフサイクル設計が不明瞭であることを示します。
所有権を明確にする
誰がDisposeを呼ぶのかを設計で決めておくことが重要です。
一般的に作成者が破棄すべきか、呼び出し元が破棄するかをドキュメント化します。
アクセシビリティの制限
外部から直接Dispose可能にしない設計も有効です。
インターフェースやラッパーで扱いを制御すると安全になります。
具体的な対処法(チェックリスト)
以下は実務で使える具体的な対処法のチェックリストです。
usingやtry/finallyで確実にDisposeする。Dispose後にアクセスしないようガードを入れる(フラグで管理)。- イベントの登録は解除する。弱参照を検討する。
- 非同期処理ではライフタイムを
awaitの前後で整合させる。 - テストで
Dispose後のアクセスケースを検証する。
usingとtry/finallyの違い
usingはtry/finallyの短縮構文です。
両者の違いはほとんどありませんが、usingはスコープで自動的にDisposeします。
// using の例
using (var conn = new SqlConnection(cs))
{
conn.Open();
// 処理
}
// connはここで破棄済み
// try/finally の例
var conn2 = new SqlConnection(cs);
try
{
conn2.Open();
// 処理
}
finally
{
conn2.Dispose();
}デバッグとログの取り方
ObjectDisposedExceptionの原因特定にはログが有効です。
どこでDisposeが呼ばれたかをログに残すと追跡が楽になります。
ログのポイント
Disposeメソッドにスタックトレースを出力する。- イベントの登録と解除をログ化する。
- 非同期処理の開始と終了をログでつなぐ。
これにより、どのタイミングでオブジェクトが破棄されたか分かります。
よくある誤解と注意点
Dispose=ガベージコレクションではない点に注意してください。
Disposeはアンマネージリソース解放のための契約です。GCはメモリ回収の仕組みです。
IsDisposedプロパティは万能ではない
IsDisposedのようなフラグを入れることは有用ですが万能ではありません。
別スレッドでDisposeが走るとチェックと処理の間に競合が起こります。
実践例:FileStreamでの安全な利用
実際のコードで確認しましょう。まずは安全なパターンです。
public void Save(string path, byte[] data)
{
// usingでスコープを限定する
using (var fs = new FileStream(path, FileMode.Create))
{
fs.Write(data, 0, data.Length);
}
// fsはここで破棄済みなので外で触らない
}
次に、非同期やイベントでの気をつける例です。
public async Task LoadAsync(string path, CancellationToken ct)
{
using (var fs = new FileStream(path, FileMode.Open))
{
var buffer = new byte[fs.Length];
await fs.ReadAsync(buffer, 0, buffer.Length, ct);
// awaitの間にDisposeされないよう注意
}
}
まとめと推奨プラクティス
ObjectDisposedExceptionは破棄済みオブジェクトへのアクセスによって発生します。
対策はライフサイクルの明確化と適切なDispose管理です。
実務で役立つ習慣を以下にまとめます。
- 作成者責任のルールをチームで合意する。
using/try/finallyを徹底して使う。- イベントの解除、非同期の整合性を確認する。
- ログを充実させて発生箇所を追跡しやすくする。
この記事を参考にC#でのObjectDisposedExceptionを正しく理解し、再発を防いでください。

#エンジニアとして、もっと自分の力を活かしたいあなたへ
「このまま今の職場にいて、成長できるんだろうか?」
C#を使っている方なら、一度はそう感じたことがあるかもしれません。
実は今、C#/.NETエンジニアの市場価値は高まっており、
年収アップ・フルリモート・自社開発企業など、選べる選択肢は確実に増えています。
もし今後、C#を活かして働きたい、あるいは開発現場で経験を積みたいとお考えなら…
自分に合った転職サービスを早めに知っておくことが大きな武器になります。
現役エンジニアからのサポート付き・無料で学べるスクール・社内SE特化の求人など、
転職支援サービスを厳選したまとめ記事をこちらで紹介しています。

ここまで読んでいただきありがとうございました。
コメント