Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to release resources

using statements, using declarations, await using, finally, IDisposable, IAsyncDisposable and finalizers. Giving back files, connections and locks on time.

Memory is the garbage collector's job. Everything else (open files, network connections, database connections, timers, event subscriptions) is yours, and the rule is: whoever creates it disposes it, as soon as they're done.

  • You create something disposable and use it in this method → a using declaration, using var writer = new StreamWriter(path);. It's disposed at the end of the enclosing block, even if an exception is thrown.
  • It should close before the block ends (release the file so another step can read it) → a using statement with braces, using (var writer = ...) { ... }.
  • You're in async code and the type has DisposeAsync → await using, so the final flush doesn't block a thread.
  • Your class keeps a disposable in a field → your class owns it, so implement IDisposable (or IAsyncDisposable) and dispose it there. Don't dispose what a caller handed you.
  • Cleanup that isn't a Dispose (reset a flag, stop a stopwatch) → try/finally.

You'll see finalizers (~OrderLog()) and the Dispose(bool) pattern in older code. New classes that only own managed objects don't need them.

Why it matters

Formal. An object on the managed heap may be collected once no chain of references leads to it from a root (a live local variable or parameter, a static field, a CPU register, a GC handle). When the collector runs is up to the runtime, mostly driven by allocation. The .NET GC is generational: new objects start in generation 0; objects that survive a collection are typically promoted to generation 1 and then 2, which are collected less often. The GC reclaims memory only; files, sockets and other resources are released by Dispose, not by collection.

In plain English. .NET cleans up objects for you, but only the ones your program can no longer reach by following references from anything it's still using. It does the cleanup when it decides to, not the moment you stop using something. New objects are checked often; objects that keep surviving get checked less, because long-lived objects tend to stay alive. The cleanup is for memory only. Open files still need closing yourself.

Example. After customers.Clear(), every customer object that nothing else refers to becomes collectable. An object still referenced from a static list or an event subscription never is, however long ago you last used it.

Why this rule exists. Freeing exactly what can't be reached is always safe, and checking young objects most often finds most garbage cheaply.

The GC frees memory for objects nothing can reach, when it decides to. It never closes your file on time.

Formal. StreamWriter and FileStream hold written data in an in-memory buffer and write it to the operating system when the buffer fills, on Flush(), or on Dispose(). Dispose also releases the file handle. The runtime does not flush a writer that was never disposed when the process exits, so its buffered text is lost. A using statement or declaration calls Dispose when its scope ends, even when an exception is thrown.

In plain English. A writer collects your text in a holding area and sends it to the file in batches. Closing the writer sends the last batch and lets go of the file. Forget to close it, and the last batch never arrives. The file can be left empty. Writing using in front of the writer makes C# close it for you at the end of the block, even if something goes wrong.

Example. var w = new StreamWriter("log.txt"); w.WriteLine("hi"); with no Dispose can leave log.txt at 0 bytes. using var w = new StreamWriter("log.txt"); writes the line when w goes out of scope.

Why this rule exists. Writing to disk one character at a time would be very slow; buffering is fast, and disposal is how the program says "I'm done, finish up."

A writer holds text in a buffer. Until it's disposed, the file may be missing its last lines.

Letting C# call Dispose

using is a try/finally the compiler writes for you.

The first real use, and how to pick between the statement and the declaration by where the scope should end.

Reverse-order disposal of stacked resources, and the modern sealed-class Dispose.

The async twin, and the CS8417/CS8418 errors when you pick the wrong one.

Who disposes what

Services the container builds are disposed when their scope ends, so you don't dispose them yourself.

A new HttpClient per call can run out of sockets. Let IHttpClientFactory manage them.