← Debugging Guides

阅读中文版本

C# ObjectDisposedException — Using a Disposed Resource

Fix ObjectDisposedException by respecting using scopes, not capturing disposed HttpClient or streams, and making ownership clear.

intermediatedebuggingcsharpdisposelifetime

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 ObjectDisposedException

Interpretation

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 | head

Interpretation

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.