Dispose, when is it called?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Dispose is not called by the garbage collector just because an object became unreachable. In .NET, Dispose is part of an explicit resource-management protocol, and it runs when your code, a using statement, or a framework owner deliberately calls it.
What Dispose Is For
Managed memory is reclaimed by the garbage collector, but unmanaged or scarce resources still need prompt cleanup. Examples include:
- file handles
- database connections
- sockets
- timers
- native handles
That is why types implement IDisposable.
Implementing IDisposable does nothing by itself. The method still has to be called.
using Calls Dispose
The most common answer to "when is it called?" is: at the end of a using scope.
At the end of that scope, the compiler-generated code calls Dispose for you, even if an exception occurs inside the block.
Equivalent expanded shape:
That is the most important mental model.
Explicit Calls Also Count
You can call Dispose directly.
This works, but using is safer because it guarantees cleanup in the presence of exceptions.
In async code, use await using for types that implement IAsyncDisposable.
That is a different protocol from IDisposable, but the same ownership principle applies.
The Garbage Collector Does Not Automatically Call Dispose
This is the part people often get wrong. If an object is collected, its memory may be reclaimed, but Dispose is not invoked automatically unless some surrounding code chose to call it.
Finalizers are a separate mechanism:
A finalizer is not the same as Dispose:
- it runs nondeterministically
- it may run much later
- it exists mainly as a safety net for unmanaged cleanup
Well-behaved disposable types still expect callers to dispose them explicitly.
Dependency Injection Containers May Dispose for You
In ASP.NET Core and other DI-based frameworks, container-managed services are disposed automatically when their owning scope ends.
Typical examples:
- scoped services disposed at the end of the request
- singleton services disposed when the host shuts down
That means you should not manually dispose services you did not create yourself. Ownership matters.
The rule is simple:
- if your code created it, your code usually disposes it
- if the container created it, let the container manage disposal
Implementing the Pattern Correctly
For most modern code that wraps other disposable resources, a straightforward pattern is enough.
The key point is idempotence. Calling Dispose twice should not break the program.
For low-level types that own native handles directly, the full dispose pattern with SafeHandle or a finalizer may still be appropriate. But most application code does not need to invent finalizers manually.
Common Pitfalls
- Assuming the garbage collector will automatically call
Dispose. - Forgetting that
usingis just compiler-generatedtry/finally. - Manually disposing objects owned by a dependency injection container.
- Confusing finalizers with deterministic disposal.
- Implementing
IDisposablebut never callingDisposeanywhere.
Summary
- '
Disposeruns when code explicitly calls it, directly or throughusing.' - The garbage collector reclaims memory, but it does not guarantee
Disposewill run. - Finalizers are a separate, nondeterministic safety mechanism.
- Ownership determines who is responsible for disposal.
- In normal C# code, prefer
usingorusing varto make disposal reliable.

