C# — Quand utiliser Dispose et le mot-clé using

Ce cours explique clairement quand et pourquoi utiliser Dispose(), comment le combiner avec le mot‑clé using, comment éviter les fuites mémoire liées aux événements, et comment cela interagit ou non avec Task.Run() et l’asynchronisme en C#.


1. Pourquoi a-t-on besoin de Dispose ?

Dispose() sert à libérer des ressources non gérées par le garbage collector. Ces ressources incluent entre autres :

Règle d’or : si une classe implémente IDisposable → appelle Dispose.

2. using — la manière recommandée d’appeler Dispose automatiquement

Simplest syntax (C# 8+)

using var stream = File.OpenRead("data.txt");
using var img = Image.FromFile("photo.png");

// Dispose() sera appelé automatiquement à la fin du scope

Ancienne syntaxe

using (var connection = new SqlConnection(cs))
{
    connection.Open();
}
// Dispose() appelé ici automatiquement

Équivalent généré par le compilateur :

var connection = new SqlConnection(cs);
try
{
    connection.Open();
}
finally
{
    connection.Dispose();
}

3. Attention : Task.Run n'appelle pas Dispose

Cette erreur est très fréquente. Beaucoup pensent que await Task.Run(...) déclenche Dispose. C'est faux.

Exemple correct

using var cnn = new Cnn();

await Task.Run(() =>
{
    cnn.Run(progress, token);
});

// Ici Dispose() est appelé automatiquement

Exemple incorrect (Dispose jamais appelé)


var cnn = new Cnn();

await Task.Run(() => cnn.Run(progress, token));

// ❌ cnn.Dispose() n'est jamais appelé → fuite potentielle

4. Les fuites mémoire dues aux événements

En C#, les événements causent facilement des fuites mémoire car le publisher garde une référence forte vers l’abonné.

publisher.Changed += OnChanged; // peut empêcher le GC de collecter l'objet

Solution : se désabonner dans Dispose()

public class Listener : IDisposable
{
    private readonly Publisher _publisher;

    public Listener(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.Changed += OnChanged;
    }

    private void OnChanged(object? sender, EventArgs e)
    {
        // ...
    }

    public void Dispose()
    {
        _publisher.Changed -= OnChanged;
    }
}

Utilisation

using var listener = new Listener(publisher);

Grâce à Dispose(), l'abonnement est supprimé automatiquement → fuite évitée.

5. Objets qui DOIVENT toujours être disposés

6. Exemple complet d'une classe professionnelle utilisant IDisposable

public class Engine : IDisposable
{
    private readonly FileStream _log;
    private readonly SqlConnection _db;
    private readonly CancellationTokenSource _cts = new();
    private readonly Publisher _publisher;

    public Engine(Publisher publisher)
    {
        _log = File.OpenWrite("log.txt");
        _db = new SqlConnection("...");
        _publisher = publisher;

        _publisher.Changed += OnChanged;
    }

    public async Task RunAsync()
    {
        await _db.OpenAsync();
        await _log.WriteAsync(...);
    }

    private void OnChanged(object? s, EventArgs e)
    {
        // ...
    }

    public void Dispose()
    {
        _publisher.Changed -= OnChanged;
        _log.Dispose();
        _db.Dispose();
        _cts.Dispose();
    }
}

Utilisation

using var engine = new Engine(publisher);
await engine.RunAsync();

7. Checklist — Quand faut-il utiliser Dispose ?


Si tu veux, je peux aussi générer :

Dis‑moi simplement ce que tu veux !