C#
using
programming
memory-management
resources

When should I use using blocks in C?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, you use using blocks for objects that implement IDisposable and need deterministic cleanup. The garbage collector handles managed memory, but it does not guarantee timely release of unmanaged resources such as file handles, sockets, and database connections. This article explains when using is the right tool, what it actually guarantees, and where developers often misuse it.

What using Really Does

A using block is syntax sugar for a try/finally that calls Dispose() at the end of the scope.

Example:

csharp
1using System;
2using System.IO;
3
4using (var stream = new FileStream("data.txt", FileMode.OpenOrCreate))
5using (var writer = new StreamWriter(stream))
6{
7    writer.WriteLine("hello");
8}

The compiler effectively turns that into code that ensures Dispose() runs even if an exception is thrown inside the block.

When You Should Use It

Use using when an object:

  • implements IDisposable
  • owns an external resource
  • should be cleaned up as soon as the current scope ends

Common examples:

  • 'FileStream'
  • 'StreamReader and StreamWriter'
  • 'SqlConnection'
  • 'HttpResponseMessage'
  • 'CancellationTokenSource'

The key idea is deterministic cleanup, not memory management in the abstract.

File and Stream Example

File access is one of the clearest cases.

csharp
1using System.IO;
2
3public static string ReadFirstLine(string path)
4{
5    using var reader = new StreamReader(path);
6    return reader.ReadLine() ?? string.Empty;
7}

Without disposal, the file handle may remain open longer than intended, which can block other operations or cause resource exhaustion.

Database Connection Example

Database resources should also be disposed promptly.

csharp
1using System.Data.SqlClient;
2
3public static int CountUsers(string connectionString)
4{
5    using var connection = new SqlConnection(connectionString);
6    using var command = new SqlCommand("SELECT COUNT(*) FROM Users", connection);
7
8    connection.Open();
9    return (int)command.ExecuteScalar();
10}

Here using helps ensure the connection is returned promptly, even when an exception occurs.

using Declaration vs using Block

Modern C# also supports using declarations:

csharp
1using var stream = new FileStream("data.txt", FileMode.OpenOrCreate);
2using var writer = new StreamWriter(stream);
3
4writer.WriteLine("hello");

This is often cleaner than nested blocks. The disposal still happens at the end of the enclosing scope.

Choose between the two based on readability:

  • use a block when you want a narrow lifetime
  • use a declaration when scope-wide lifetime is fine

When using Is Not Needed

Do not use using just because an object is large or memory-heavy. using is for disposable resources, not for ordinary objects that the garbage collector handles normally.

For example, this is wrong because string does not implement IDisposable:

csharp
string name = "example";

Likewise, you do not wrap every object creation in using. Only apply it when disposal is part of the type contract.

Be Careful with Returned Objects

Do not dispose an object before returning it to the caller.

Bad:

csharp
1public static Stream OpenFile(string path)
2{
3    using var stream = new FileStream(path, FileMode.Open);
4    return stream;
5}

The returned stream is already disposed. If the caller needs to own the lifetime, do not wrap it in using inside the factory method.

Correct:

csharp
1public static Stream OpenFile(string path)
2{
3    return new FileStream(path, FileMode.Open);
4}

Now the caller is responsible for disposal.

Async Resources Need await using

Some types implement IAsyncDisposable and should be disposed asynchronously.

csharp
await using var resource = await OpenAsyncResource();
await resource.WriteAsync("hello");

This is the async equivalent of using and is important for types whose cleanup itself involves asynchronous work.

Why It Matters Even with a Garbage Collector

The garbage collector reclaims memory when it decides the object is unreachable. That does not mean it closes OS handles immediately. If a file, socket, or connection must be released now, Dispose() is the right mechanism.

That is why relying only on GC for disposable resources is a common mistake in C# code.

Common Pitfalls

  • Using using for memory-heavy objects that are not actually disposable.
  • Returning an object from a method after disposing it inside a using scope.
  • Keeping disposable resources alive longer than necessary by using overly broad scopes.
  • Forgetting about await using for async disposable types.
  • Assuming the garbage collector will release unmanaged resources promptly enough on its own.

Summary

  • Use using for objects that implement IDisposable and need deterministic cleanup.
  • Typical examples include streams, database connections, and response objects.
  • Prefer using declarations or blocks based on the lifetime you want to express.
  • Do not use using for ordinary managed objects that are not disposable.
  • Be explicit about ownership: whoever owns the resource lifetime should dispose it.

Course illustration
Course illustration

All Rights Reserved.