Exception In Dispose

ID

csharp.exception_in_dispose

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

dispose, reliability

Description

Reports Dispose() or Dispose(bool) methods that contain a throw statement. Dispose is the runtime’s standard mechanism for releasing resources, called by using statements and by finally blocks during cleanup; throwing from it masks the original exception and leaks unreleased resources.

Rationale

When an exception is already being propagated and a finally (including the implicit one generated by using) calls Dispose, a throw from Dispose replaces the original exception. The information about what really went wrong is lost, additional cleanup that should still happen is skipped, and code that legitimately relies on Dispose being safe to call (idempotent, non-throwing) is left in an undefined state.

The accepted convention is that Dispose must be tolerant: it should log or swallow unexpected conditions, never propagate them.

public class Connection : IDisposable
{
    public void Dispose()
    {
        if (state != Closed)
            throw new InvalidOperationException("not closed"); // FLAW
    }
}

public class ConnectionTemplate : IDisposable
{
    protected virtual void Dispose(bool disposing)
    {
        if (disposing && state != Closed)
            throw new InvalidOperationException("not closed"); // FLAW
    }

    public void Dispose() => Dispose(true);
}

public class ConnectionOk : IDisposable
{
    public void Dispose()                                       // OK
    {
        try { Close(); } catch (Exception ex) { Log(ex); }
    }
}

Remediation

Make Dispose non-throwing: log unexpected conditions, wrap risky cleanup in try/catch, and never propagate exceptions out of Dispose. If a resource cannot be released, mark the object as unusable and let later operations fail with a clear error instead.