In programs that handle external resources (unmanaged resources) such as file operations or database connections, “cleanup” after use is extremely important. Forgetting to release (Dispose) resources can cause files to remain locked or lead to memory leaks.
In C#, by applying the using statement to objects that implement the IDisposable interface, you can ensure that the Dispose method is called even if an exception occurs.
This article explains the mechanism of the using statement and the modern syntax introduced in C# 8.0, using a log file writing process as an example.
Role and Mechanism of the using Statement
The using statement is “syntactic sugar” that is automatically expanded into a try-finally block at compile time.
While it is possible for developers to manually write a finally block and call the Dispose method within it, the code becomes redundant and carries the risk of forgetting to call it. Using using guarantees that resource release is performed automatically the moment the scope is exited.
Comparison: Manual Release vs. using
The following two code snippets behave almost identically after compilation.
1. Redundant Description Using try-finally (Not Recommended)
Exception handling and resource release are mixed, reducing readability. You also need to consider null checks for the variable.
var writer = new System.IO.StreamWriter("log.txt");
try
{
writer.WriteLine("Process started");
}
finally
{
// Must explicitly call Dispose
if (writer != null)
{
((IDisposable)writer).Dispose();
}
}
2. Description Using the using Statement (Recommended)
It is concise, and the valid range (scope) of the resource becomes clear.
using (var writer = new System.IO.StreamWriter("log.txt"))
{
writer.WriteLine("Process started");
} // Dispose() is automatically called here
Practical Code Example: Writing to a Log File
Here, we introduce a more modern and concise way to write code using the “using declaration” introduced in C# 8.0.
Since it does not require deep nesting, and the variable scope continues to the end of the block, the entire code becomes flat and easier to read.
using System;
using System.IO;
using System.Text;
namespace LoggerApplication
{
class Program
{
static void Main()
{
string logFilePath = "application.log";
try
{
WriteLog(logFilePath, "[Info] Application started.");
WriteLog(logFilePath, "[Info] Data loading complete.");
Console.WriteLine("Log writing complete.");
}
catch (IOException ex)
{
Console.Error.WriteLine($"File operation error: {ex.Message}");
}
}
/// <summary>
/// Appends a log message to the specified file.
/// </summary>
static void WriteLog(string path, string message)
{
// "using declaration" (C# 8.0 and later)
// Instead of using a block { }, attach 'using' to the variable declaration.
// Dispose is automatically called when exiting this method (WriteLog).
using var writer = new StreamWriter(path, append: true, Encoding.UTF8);
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
writer.WriteLine($"{timestamp} {message}");
// Explicit calls to Close() or Dispose() are not required.
}
}
}
Execution Result (Content of application.log)
2025-10-25 10:00:00 [Info] Application started.
2025-10-25 10:00:01 [Info] Data loading complete.
Technical Points and IDisposable
1. Implementation of the IDisposable Interface
Only classes that implement the System.IDisposable interface can be targets of the using statement. This interface defines a single method: void Dispose().
When performing resource management (file handles, database connections, etc.) in a custom class, you must design it to implement this interface so that users can utilize using.
2. Difference Between Dispose and Close
Classes like StreamWriter or SqlConnection may have a Close method in addition to the Dispose method. In most cases, these are functionally equivalent, and the implementation often calls Dispose internally within Close. However, as a standard .NET resource release practice, calling Dispose via IDisposable (i.e., using the using statement) is recommended.
3. Asynchronous Dispose (IAsyncDisposable)
From C# 8.0, the IAsyncDisposable interface and await using syntax were introduced for performing resource release asynchronously. If the cleanup process itself involves potential I/O waits, such as file I/O or network communication, using this prevents blocking threads during destruction.
// Asynchronous resource release
await using var writer = new StreamWriter("log.txt");
await writer.WriteLineAsync("Log message");
Summary
The using statement is one of the most basic and important features for preventing resource leaks and building robust applications. Especially by leveraging the “using declaration” from C# 8.0 onwards, safe resource management becomes possible without sacrificing code readability. Make it a habit to always use using when handling classes that implement IDisposable.
