Skip to Content
Course content

85: IDisposable and the using Statement

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're renting a high-end power tool from a local hardware store—say, a heavy-duty industrial pressure washer. You sign a contract, take the tool home, and get your driveway sparkling. But here's the catch: the store only has three of these. If you just leave the pressure washer in your garage and forget about it, the store can't rent it to anyone else, and you're still being charged by the day. Eventually, the store runs out of tools entirely, even though nobody is actually using them. To fix this, you have to explicitly bring the tool back to the store to "close out" your rental.

In C#, the Garbage Collector (GC) is like a cleaning crew that comes through your house every once in a while to throw away trash. It's great for memory, but it doesn't know how to "return the rental tool." Things like file handles, database connections, or network sockets are "unmanaged resources." The GC knows how to reclaim the memory the object took up, but it doesn't know that it needs to tell the Operating System, "Hey, I'm done with this file; you can let another program open it now."

Why the Garbage Collector isn't enough

I've seen plenty of juniors assume that because C# has automatic memory management, they never have to worry about cleanup. That's a dangerous assumption. If you open a StreamWriter to a log file in a loop and never close it, you'll eventually hit a IOException because the OS thinks the file is still being used by your program, even if you aren't actively writing to it.

This is where IDisposable comes in. It's essentially a contract that says, "This object holds onto something that needs to be explicitly released." When a class implements IDisposable, it promises to provide a Dispose() method where you can put your cleanup logic.

// A simplified look at how a resource-heavy class is structured
public class ResourceHolder : IDisposable
{
    private bool _disposed = false;

    public void DoWork() 
    {
        if (_disposed) throw new ObjectDisposedException("ResourceHolder");
        Console.WriteLine("Doing some heavy lifting with a file or socket...");
    }

    public void Dispose()
    {
        // This is where the "return the tool to the store" logic goes
        Console.WriteLine("Cleaning up unmanaged resources...");
        _disposed = true;
        
        // Tell the GC we don't need to run the finalizer for this object
        GC.SuppressFinalize(this);
    }
}

The "Auto-Return" of the using Statement

You could manually call .Dispose() in a finally block, but that's verbose and easy to forget. I'll be honest: I've spent way too many hours debugging memory leaks because someone forgot to call dispose in an edge-case catch block. C# gives us the using statement to solve this. It's essentially syntactic sugar that guarantees Dispose() is called the moment the code exits the block, regardless of whether it finished successfully or threw an exception.

Here is the modern, concise way to do it (the using declaration), which I prefer for its cleanliness:

public void WriteLog(string message)
{
    // The 'using' keyword here means: "Call Dispose() when this variable goes out of scope"
    using var writer = new StreamWriter("log.txt", append: true);
    writer.WriteLine($"{DateTime.Now}: {message}");
    
    // No need to call writer.Dispose() or writer.Close(). 
    // It happens automatically right here at the closing brace.
}

If you're working in an older codebase, you'll see the using (...) { } block version. They do exactly the same thing; the block version just lets you control the exact moment the resource is released if you have a very long method and don't want to hold the resource until the very end.

When should you actually implement IDisposable?

You don't need this for every class. In fact, adding it where it isn't needed just creates noise. You only implement IDisposable if your class:

  • Owns an object that itself implements IDisposable (like a HttpClient or SqlConnection).
  • Directly interacts with unmanaged handles (like Win32 API calls or raw pointers in unsafe code).

If your class just holds a list of strings and a couple of integers, leave it alone. Let the Garbage Collector do its thing.




📋 Practical Task

Implementing a Disposable Database Connection Simulator

Since we aren't connecting to a live server in this exercise, you're going to build a simulator. Your goal is to create a system that mimics a database connection that must be closed to prevent "connection leaks."

Requirements:

  • Create a class called DbConnectionSimulator that implements IDisposable.
  • Add a private boolean field _isOpen. Set it to true in the constructor.
  • Create a method ExecuteQuery(string query). This method should check if _isOpen is true; if it is, print "Executing query: [query]". If it's false, throw an ObjectDisposedException.
  • Implement the Dispose() method to set _isOpen to false and print "Connection closed safely."
  • In your Main method, use a using declaration to instantiate the simulator and run a query.
  • As a test, try to call ExecuteQuery after the using scope has ended to verify that the ObjectDisposedException is thrown.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.