Dispose Before Scope

ID

csharp.dispose_before_scope

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

CWE:772, idisposable, reliability, resource-leak

Description

Reports local IDisposable variables that exit their enclosing method without a matching Dispose() call. Leaking an unmanaged resource (file handles, sockets, database connections) keeps it allocated until the finaliser runs, which on hot paths is enough to exhaust the process handle table. Passing the variable to the constructor of another disposable only counts as ownership transfer when that wrapper itself escapes the method (returned, assigned to a field, or stored in a local that is itself returned or disposed) — a wrapper built only to be chained into a further call and discarded does not prove anything was disposed.

Rationale

The IDisposable contract is the .NET runtime’s only deterministic mechanism for releasing native resources. Forgetting to call Dispose() defers the cleanup to the GC, which may take seconds or minutes; under load, the leak is fast enough to break the program before the GC notices.

public class Reader
{
    public string Bad(string path)
    {
        var stream = new FileStream(path, FileMode.Open);          // FLAW
        return new StreamReader(stream).ReadToEnd();
    }

    public string OkUsing(string path)
    {
        using (var stream = new FileStream(path, FileMode.Open))   // OK
        {
            return new StreamReader(stream).ReadToEnd();
        }
    }

    public string OkUsingDecl(string path)
    {
        using var stream = new FileStream(path, FileMode.Open);    // OK
        return new StreamReader(stream).ReadToEnd();
    }

    public string OkManual(string path)
    {
        var stream = new FileStream(path, FileMode.Open);          // OK, explicit Dispose
        try   { return new StreamReader(stream).ReadToEnd(); }
        finally { stream.Dispose(); }
    }

    public HttpClient OkOwnershipTransfer()
    {
        var handler = new HttpClientHandler();                     // OK, wrapped by a disposable owner
        return new HttpClient(handler);
    }

    public string BadDiscardedWrapper()
    {
        var handler = new HttpClientHandler();                     // FLAW, the HttpClient wrapping it is discarded
        return new HttpClient(handler).ToString();
    }
}

Remediation

Wrap the declaration in a using statement or the using var declaration form so the compiler emits the matching Dispose() call. When the disposable must outlive the method, transfer ownership explicitly (assign to a field, return it, pass it to the constructor of a disposable wrapper that takes over disposal, or pass it to a registry that documents disposal responsibility).