C# programming
.NET framework
memory management
IDisposable interface
coding best practices

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.

csharp
1public sealed class SampleResource : IDisposable
2{
3    public void Dispose()
4    {
5        Console.WriteLine("Dispose called");
6    }
7}

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.

csharp
using var stream = File.OpenRead("data.txt");
Console.WriteLine(stream.Length);

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:

csharp
1FileStream stream = File.OpenRead("data.txt");
2try
3{
4    Console.WriteLine(stream.Length);
5}
6finally
7{
8    stream.Dispose();
9}

That is the most important mental model.

Explicit Calls Also Count

You can call Dispose directly.

csharp
var stream = File.OpenRead("data.txt");
Console.WriteLine(stream.Length);
stream.Dispose();

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.

csharp
await using var resource = new SomeAsyncResource();

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:

csharp
1public class FinalizerDemo
2{
3    ~FinalizerDemo()
4    {
5        Console.WriteLine("finalizer ran");
6    }
7}

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.

csharp
1public sealed class Worker : IDisposable
2{
3    private readonly FileStream _stream;
4    private bool _disposed;
5
6    public Worker(string path)
7    {
8        _stream = File.OpenRead(path);
9    }
10
11    public void Dispose()
12    {
13        if (_disposed) return;
14        _stream.Dispose();
15        _disposed = true;
16    }
17}

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 using is just compiler-generated try/finally.
  • Manually disposing objects owned by a dependency injection container.
  • Confusing finalizers with deterministic disposal.
  • Implementing IDisposable but never calling Dispose anywhere.

Summary

  • 'Dispose runs when code explicitly calls it, directly or through using.'
  • The garbage collector reclaims memory, but it does not guarantee Dispose will run.
  • Finalizers are a separate, nondeterministic safety mechanism.
  • Ownership determines who is responsible for disposal.
  • In normal C# code, prefer using or using var to make disposal reliable.

Course illustration
Course illustration

All Rights Reserved.