IDisposable et le modèle using en C#
Apprenez IDisposable et le modèle using en C# pour gérer les ressources correctement et éviter les fuites mémoire.
En C#, l’interface IDisposable est conçue pour libérer correctement les ressources utilisées en dehors de la mémoire
(par exemple les fichiers, les connexions réseau, les bases de données, les objets GDI, etc.).
Au lieu d’attendre que le système les nettoie automatiquement,
le nettoyage déterministe (prévisible) est effectué avec la méthode Dispose et la structure using.
Qu’est-ce que IDisposable ?
L’interface IDisposable ne contient qu’une seule méthode :
public interface IDisposable
{
void Dispose();
}
Lorsqu’une classe implémente IDisposable, elle doit libérer les ressources qu’elle possède —
qu’elles soient gérées (managed) ou non gérées (unmanaged) — dans la méthode Dispose().
Le ramasse-miettes (GC) de .NET ne peut pas nettoyer automatiquement les ressources non gérées,
c’est pourquoi l’appel à Dispose() est essentiel.
Exemple simple d’implémentation de IDisposable
Dans l’exemple suivant, la classe FileWriter ouvre un flux de fichier et implémente IDisposable.
Lorsque Dispose() est appelée, le fichier est fermé automatiquement.
using System;
using System.IO;
class FileWriter : IDisposable
{
private readonly StreamWriter _writer;
private bool _disposed = false;
public FileWriter(string path)
{
_writer = new StreamWriter(path);
}
public void Write(string text)
{
if (_disposed)
throw new ObjectDisposedException(nameof(FileWriter));
_writer.WriteLine(text);
}
public void Dispose()
{
if (!_disposed)
{
_writer.Close();
_writer.Dispose();
_disposed = true;
Console.WriteLine("Fichier fermé et ressources libérées.");
}
}
}
class Program
{
static void Main()
{
var writer = new FileWriter("test.txt");
writer.Write("Bonjour le monde !");
writer.Dispose(); // ressources libérées
}
}
Gestion des ressources avec using
Le mot-clé using permet de nettoyer automatiquement les objets implémentant IDisposable.
Lorsque le bloc using se termine, la méthode Dispose() de l’objet est appelée automatiquement.
using (var file = new StreamWriter("log.txt"))
{
file.WriteLine("Application démarrée : " + DateTime.Now);
} // Dispose() est appelée automatiquement ici
Cela évite toute fuite de ressource, même en cas d’erreur.
Un bloc using est équivalent à une structure try/finally :
var file = new StreamWriter("log.txt");
try
{
file.WriteLine("Application démarrée.");
}
finally
{
file.Dispose();
}
Plusieurs using sur une même ligne
Depuis C# 8.0, la syntaxe « using declaration » permet un code plus concis.
Sans accolades, Dispose() est toujours appelé automatiquement à la fin de la portée.
using var file = new StreamWriter("output.txt");
file.WriteLine("Ce fichier se fermera automatiquement à la fin du programme.");
Le modèle Dispose (utilisation avancée)
Si une classe contient des ressources non gérées ou si elle doit être dérivée par des classes
possédant elles-mêmes des objets IDisposable, il faut appliquer le modèle Dispose.
Ce modèle utilise la méthode Dispose(bool disposing) pour distinguer
la libération des ressources gérées et non gérées.
using System;
class GestionnaireRessource : IDisposable
{
private IntPtr _ressourceNonGeree; // exemple : handle de fichier, socket, objet GDI
private bool _disposed = false;
public GestionnaireRessource()
{
_ressourceNonGeree = IntPtr.Zero; // initialisation d’exemple
}
// Méthode publique Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // empêche l’appel du finaliseur
}
// Méthode protégée et virtuelle, extensible par les classes dérivées
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// libérer les ressources gérées ici
}
// libérer les ressources non gérées ici
if (_ressourceNonGeree != IntPtr.Zero)
{
// exemple : CloseHandle(_ressourceNonGeree);
_ressourceNonGeree = IntPtr.Zero;
}
_disposed = true;
}
// Finaliseur (garantie pour les ressources non gérées)
~GestionnaireRessource()
{
Dispose(false);
}
}
Cette structure empêche les appels de finaliseur inutiles grâce à GC.SuppressFinalize()
et garantit la libération sécurisée des ressources non gérées.
Gestion asynchrone des ressources : IAsyncDisposable
Introduite avec C# 8.0, l’interface IAsyncDisposable est utilisée pour le nettoyage asynchrone des ressources.
Par exemple, une connexion réseau ou un flux peut être fermé de façon asynchrone.
using System;
using System.IO;
using System.Threading.Tasks;
class EcrivainLog : IAsyncDisposable
{
private readonly StreamWriter _writer = new StreamWriter("async_log.txt");
public async ValueTask DisposeAsync()
{
await _writer.FlushAsync();
_writer.Dispose();
Console.WriteLine("Écrivain de log asynchrone fermé.");
}
public async Task EcrireAsync(string message)
{
await _writer.WriteLineAsync(message);
}
}
class Program
{
static async Task Main()
{
await using var log = new EcrivainLog();
await log.EcrireAsync("Programme démarré.");
}
}
Erreurs courantes et points d’attention
- Dispose() doit pouvoir être appelée plusieurs fois sans provoquer d’erreur (idempotente).
GC.SuppressFinalize()ne doit être appelée qu’après la libération complète des ressources gérées.- Dans un bloc
using, aucune fuite de ressource ne peut se produire ; en dehors,Dispose()doit être appelée manuellement. - Si des ressources non gérées existent, n’oubliez pas d’ajouter un finaliseur.
- Utilisez
IAsyncDisposablepour les ressources asynchrones.
Exemple : service de gestion de fichiers
Dans l’exemple suivant, une classe de service gère des ressources telles que FileStream et StreamReader.
Grâce à using, les fichiers sont automatiquement fermés à la fin de l’opération.
using System;
using System.IO;
class ServiceFichier
{
public string Lire(string chemin)
{
using var sr = new StreamReader(chemin);
return sr.ReadToEnd();
}
public void Ecrire(string chemin, string donnees)
{
using var sw = new StreamWriter(chemin, append: true);
sw.WriteLine(donnees);
}
}
class Program
{
static void Main()
{
var service = new ServiceFichier();
service.Ecrire("rapport.txt", "Nouvelle entrée ajoutée.");
Console.WriteLine(service.Lire("rapport.txt"));
}
}
TL;DR
- IDisposable permet de libérer manuellement les ressources via
Dispose(). - La structure using appelle automatiquement
Dispose()et évite les fuites de ressources. - Le modèle Dispose assure un nettoyage sûr des ressources gérées et non gérées.
- IAsyncDisposable libère les ressources de manière asynchrone (par ex. fichier, réseau).
- GC.SuppressFinalize() améliore les performances en évitant les appels de finaliseur inutiles.
Articles connexes
Classes, Objets, Propriétés et Méthodes en C#
Découvrez comment les classes, objets, propriétés et méthodes en C# constituent les fondements de la programmation orientée objet.
Entrées/Sorties de fichiers et API Stream en C#
Apprenez les entrées/sorties de fichiers et l’API Stream en C# pour lire et écrire des données efficacement.
Gestion de la mémoire et Garbage Collector en C#
Apprenez la gestion de la mémoire et le garbage collector en C# pour comprendre le cycle de vie des objets.
Gestion des exceptions en C# (try, catch, finally)
Apprenez à gérer les exceptions en C# avec les blocs try, catch et finally afin de traiter les erreurs de manière sûre avec exemples.
Interop en C# (Utilisation de bibliothèques C/C++)
Apprenez l’Interop en C# pour utiliser des bibliothèques C/C++, y compris P/Invoke et la gestion du code non managé.