C# ObjectDisposedException — Using a Disposed Resource
Fix ObjectDisposedException by respecting using scopes, not capturing disposed HttpClient or streams, and making ownership clear.
Symptoms
- System.ObjectDisposedException: Cannot access a disposed object
- A response stream is used after its HttpResponseMessage is disposed
- An injected HttpClient works once then fails
Possible causes
- A resource is used after its using block or Dispose call has exited
- A stream reader outlives the response that owned the stream
- HttpClient or DbContext is disposed while callers still hold a reference
Diagnosis steps
Find the disposal point in the stack
dotnet run 2>&1 | grep -A 3 ObjectDisposedExceptionInterpretation
The ObjectName property in the message names the disposed instance, pointing to its owner.
Check for using and async combination
grep -rn "using var\|using (" *.cs | headInterpretation
A using var at method scope disposes at method end; a captured reference outliving it fails.
Root cause
IDisposable objects become unusable once disposed. ObjectDisposedException is thrown when code calls a member of an object whose resources are already released, usually because ownership or lifetime was misjudged.
Fix
Keep the resource alive for as long as it is used: copy needed data out before leaving the using scope, use IHttpClientFactory for HttpClient, and scope DbContext to the request.
Risk note
Removing using to avoid the exception leaks the resource; fix the lifetime rather than dropping disposal.
Verify the fix
Rerun the scenario and confirm no ObjectDisposedException; use a diagnostic test with a second call to prove the resource is still valid when accessed.
Symptoms
This guide profiles the failure behind “C# ObjectDisposedException — Using a Disposed Resource”. It walks through the observable symptoms, a reproducible diagnosis path, the underlying root cause, the concrete fix, and how to verify the change and prevent a regression.
Observed symptoms include:
- System.ObjectDisposedException: Cannot access a disposed object
- A response stream is used after its HttpResponseMessage is disposed
- An injected HttpClient works once then fails
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Find the disposal point in the stack
dotnet run 2>&1 | grep -A 3 ObjectDisposedException
The ObjectName property in the message names the disposed instance, pointing to its owner.
Step 2: Check for using and async combination
grep -rn "using var\|using (" *.cs | head
A using var at method scope disposes at method end; a captured reference outliving it fails.
Likely contributors, in order of frequency:
- A resource is used after its using block or Dispose call has exited
- A stream reader outlives the response that owned the stream
- HttpClient or DbContext is disposed while callers still hold a reference
Root Cause
IDisposable objects become unusable once disposed. ObjectDisposedException is thrown when code calls a member of an object whose resources are already released, usually because ownership or lifetime was misjudged.
The Fix
Keep the resource alive for as long as it is used: copy needed data out before leaving the using scope, use IHttpClientFactory for HttpClient, and scope DbContext to the request.
Watch out: Removing using to avoid the exception leaks the resource; fix the lifetime rather than dropping disposal.
// ✅ HttpClient via factory, no manual dispose
public sealed class ApiClient(IHttpClientFactory f)
{
public async Task<string> GetAsync(CancellationToken ct)
{
var client = f.CreateClient("api");
return await client.GetStringAsync("/health", ct);
}
}
// ✅ read the body before the response leaves the using scope
using var resp = await http.GetAsync(url);
var text = await resp.Content.ReadAsStringAsync(); // body copied out
// return text and keep using it after resp is disposed
Verify and Prevent
Rerun the scenario and confirm no ObjectDisposedException; use a diagnostic test with a second call to prove the resource is still valid when accessed.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.