Implement IDisposable Correctly

In this post, we are going to look in detail at what the IDisposable interface is, and we will also learn to use it with an example in C#. 

 

 

1 - What is the IDisposable interface

The IDisposable interface is a very simple interface. If you do not know what interfaces are

The IDisposable interface is also very straightforward—it is an interface with a single method that provides a mechanism to free objects from memory that are not managed by the system. 

public interface IDisposable
{
    void Dispose();
}

The important point about this interface is that it is defined in the core of the .NET language, which means that any class in our programs can implement IDisposable.

 

And you need to know if you are using an instance of a class that is disposable in order to use the pattern correctly.

 

1.1 - Unmanaged resources

When we implement IDisposable, we are telling consumers of our class that we are using resources not managed by the system, and because of that, these resources must be manually indicated to be cleaned up by the garbage collector

 

Implementing, using, and understanding IDisposable is easy, but the documentation is often confusing since it always refers to "unmanaged resources", which may make you think it does not affect you, but it actually does. 

 

When working on a .NET application that interacts with the file system, or an SQL query, or most cloud services like S3 in amazon web services, you are using resources not managed by the system. 

recursos no adminsitrados

Example: In the specific case of SQL connections, we use System.Data, which is managed by the system, but the actual connection to the database is NOT, so we must call Dispose to free up memory. 

 

But it is not enough to just call the Dispose() method, you also have to know what to do inside it; for example, in a database connection you must close the connection. 

 

 

2 - How to use IDisposable

If we instantiate a class that implements IDisposable, we must manually manage its lifecycle. 

To do this, we instantiate it within a using block, which under the hood compiles to a try{}finally{}{} calling the .Dispose() method in the finally block automatically. 

public class EjemploClaseDisposable : IDisposable
{
    public void Metodo1Ejemplo()
    {
        Console.WriteLine("Ejemplo");
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}

[TestMethod]
public void EjemploUsing()
{
    using (EjemploClaseDisposable ej = new EjemploClaseDisposable())
    {
        ej.Metodo1Ejemplo();
    }
}

Calling the dispose method means telling the garbage collector to free up the resources used. 

 

The example we just saw is equivalent to creating the object, using it, and then calling dispose: 

[TestMethod]
public void EjemploInstanciar()
{
    EjemploClaseDisposable ej = new EjemploClaseDisposable();
    ej.Metodo1Ejemplo();
    ej.Dispose();
}

It is recommended to call the .Dispose() method as soon as possible in your code to free up the resource.

 

In most real use cases, our problem will be with memory, but for example, we can also face it when connecting to a database. When we make calls to the database, they are limited to a certain number, called the “connection pool”; once you exceed this number, the application will hang. We will see an example later.

 

2.1 - What happens if we do not use Dispose

If we do not use .Dispose() or do not instantiate the object within a using block, we will not get any error. Neither at runtime nor at compile time.

 

But we will have a bug in our code that is probably not easy to catch quickly, since it depends a lot on what the class does and how much memory it uses. We are not telling the garbage collector to clean up resources when they are no longer needed. 

 

In the worst-case scenarios, a class may be referencing an object that should have been cleaned up, but since it was not disposed, that memory is never released until the application stops running or an exception occurs, like OutOfMemoryException or any other. 

 

These types of errors are hard to detect, or at least not detected quickly, since it is possible for things to work on your machine or on test environments, but when you deploy to production, it crashes because production has a higher workload. 

 

 

3 - IDisposable Example

For this example, I tried to make a “common” example from the business world.

Suppose we have to query a database. In this example, I simply read the current date (to save time creating a table and data).

We are going to use the connection seen in the post how to connect to a database.

 

So, we have a class that allows us to query the database, and to keep things simple, I will only retrieve the current date. If we make a single call, everything works fine.

public class DatabaseWrapper
{
    private MySqlConnection _connection;

    public string GetFecha()
    {
        if (_connection == null)
        {
            _connection = new MySqlConnection("Server=127.0.0.1;Port=3306;Database=personal;Uid=root;password=test;");
            _connection.Open();
        }
        using (var command = _connection.CreateCommand())
        {
            command.CommandText = "SELECT NOW()";
            return command.ExecuteScalar().ToString();
        }
    }
}

[TestMethod]
public void EjemploejecutarDb()
{
    var wrapper = new DatabaseWrapper();
    var fecha = wrapper.GetFecha();
    Console.WriteLine(fecha);
    Assert.IsTrue(true);
}

The problem comes when, instead of a single call, we run a loop with a thousand calls.

[TestMethod]
public void EjemploLeerNoDisposed()
{
    for (int i = 0; i < 1000; i++)
    {
        var wrapper = new DatabaseWrapper();
        var fecha = wrapper.GetFecha();
        Console.WriteLine(fecha);
    }
    Assert.IsTrue(true);
}

As we can see, this code doesn't work; the error we get is a database timeout indicating that we've reached the maximum number of connections because these connections are NEVER released. 

Test method ProgramacionAvanzada.Test.Test_IDisposable.EjemploEjecutarDbNoDisposable threw exception: 
MySql.Data.MySqlClient.MySqlException: error connecting: Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.  This may have occurred because all pooled connections were in use and max pool size was reached.

Additionally, we can query the database and it will show us this maximum number that we have.

USE information_schema;
SELECT COUNT(*) FROM PROCESSLIST;

In .NET, we can change the maximum number of connections to the database by including the attribute "Max pool size={number}" when specifying the connection string; however, this is obviously not an acceptable solution, since it can eventually grow without limit. 

 

What we need to do to fix this situation is to implement the IDisposable interface,

when we implement this interface, we are telling the code that it must release this connection when execution ends. 

public class DatabaseWrapperDispose : IDisposable
{
    private MySqlConnection _connection;

    public string GetFecha()
    {
        if (_connection == null)
        {
            _connection = new MySqlConnection("Server=127.0.0.1;Port=3306;Database=personal;Uid=root;password=test;");
            _connection.Open();
        }
        using (var command = _connection.CreateCommand())
        {
            command.CommandText = "SELECT NOW()";
            return command.ExecuteScalar().ToString();
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        if (disposing)
        {
            _connection.Close();
            _connection.Dispose();
        }
    }

    ~DatabaseWrapperDispose()
    {
        Dispose(false);
    }
}

And of course, we must remember to execute the code using our using block or a try{} finally{}

[TestMethod]
public void EjemploLeerDisposed()
{
    for (int i = 0; i < 1000; i++)
    {
        using (var wrapper = new DatabaseWrapperDispose())
        {
            var fecha = wrapper.GetFecha();
            Console.WriteLine(fecha);
        }
    }
    Assert.IsTrue(true);
}

[TestMethod]
public void EjemploLeerDisposedTryFinally()
{
    for (int i = 0; i < 1000; i++)
    {
        DatabaseWrapperDispose wrapper = null;
        try
        {
            wrapper = new DatabaseWrapperDispose();
            var fecha = wrapper.GetFecha();
        }
        finally
        {
            wrapper.Dispose();
        }
    }
    Assert.IsTrue(true);
}

 

 

Conclusion

Many programs suffer from serious issues because they do not use Dispose correctly or because Dispose is not implemented at all. 

 

The correct use of Dispose is tricky, since we must know and understand every class we use, not just our own, but also those provided by the framework. 

 

Using Dispose properly brings code stability, and nowadays, for example, AWS (or Azure with Azure Functions) charges you for memory used and execution time, so a memory usage issue can end up costing thousands more in usage fees. 

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café