ObjectDisposedException は、「すでに破棄(Dispose)されたオブジェクトを使った」ときに出る。HttpClient・Stream・DbContext・CancellationTokenSource などでよく見る。
この記事では、よくある発生原因と対策の型をざっくりまとめる。
using を抜けたあと・DI スコープ終了後・親が子をまとめて Dispose したあとHttpClient は都度 Dispose しすぎない(使い回し/IHttpClientFactory)Stream stream;
using (var fs = File.OpenRead(path))
{
stream = fs;
}
var b = stream.ReadByte(); // ObjectDisposedException
参照は残っていても、中身は破棄済み。
リクエスト終了後に DbContext をキャッシュして使い回す、バックグラウンドに渡したままスコープが終わる、などが典型。
// イメージ: リクエストスコープの DbContext をフィールドに保存して後で使う
_db = context; // リクエスト終了後に Dispose される
// ...
await _db.SaveChangesAsync(); // ObjectDisposedException
IHttpClientFactory か、長寿命の使い回しasync Task<string> ReadAsync()
{
using var reader = new StreamReader(path);
return await reader.ReadToEndAsync();
// using はメソッド終了時に Dispose
// 続きの処理で reader を使うと危険
}
「起動したタスクが、親の using 終了後に動く」パターンが特に危険。タスクの完了まで Dispose しない。
Dispose 自体は複数回呼ばれてもよい実装が多いが、Dispose 後に通常操作すると ObjectDisposed になる。キャンセル済みの CancellationTokenSource 再使用なども同類。
await using var stream = File.OpenRead(path);
var bytes = new byte[stream.Length];
await stream.ReadExactlyAsync(bytes);
// ここで初めて Dispose
長寿命サービスから Scoped が必要なら、IServiceScopeFactory で短いスコープを作ってその中で使う。
builder.Services.AddHttpClient("backend", c =>
{
c.BaseAddress = new Uri("https://example.invalid/");
});
var client = _httpClientFactory.CreateClient("backend");
ObjectDisposedException = 捨てたあとに触った