IDisposable : l'arme secrète des performances dont les documentations ne parlent pas
Avez-vous déjà remarqué que votre application C# commence à « ralentir » après plusieurs heures de fonctionnement ? Ou pourquoi la base de données cesse soudainement de répondre aux requêtes ? Dans 80 % des cas, un seul interface est en cause — ou plutôt, son utilisation incorrecte. Il s'agit de IDisposable.
Sur Stack Overflow, la question « Proper use of the IDisposable interface » a rassemblé des milliers de vues et des centaines de discussions. Et ce n'est pas un hasard : une mauvaise gestion des ressources est le principal ennemi des performances dans .NET. Voyons comment transformer IDisposable de « magie noire » en un puissant outil d'optimisation.
Pourquoi IDisposable n'est pas simplement une « case à cocher » dans le code ?
De nombreux développeurs perçoivent IDisposable comme une obligation ennuyeuse : « il faut l'implémenter — je l'implémente ». Mais en réalité, c'est votre meilleur allié dans la lutte pour les performances. Imaginez que votre application est un bureau. Le Garbage Collector (GC) est le nettoyeur qui vient la nuit. Et IDisposable est votre assistant personnel qui nettoie immédiatement, sans attendre le matin.
Sans une utilisation correcte d'IDisposable, vous risquez :
- Des fuites mémoire (memory leaks)
- Le blocage de fichiers et de connexions à la base de données
- Des chutes de performances inattendues
- Des problèmes de mise à l'échelle
Les bases : qu'est-ce qu'IDisposable et comment fonctionne-t-il ?
IDisposable est une interface qui contient une seule méthode : Dispose(). Son rôle est de libérer les ressources non managées (fichiers, connexions réseau, handles de fenêtres) immédiatement dès qu'elles ne sont plus nécessaires.
public class ResourceHolder : IDisposable{ private IntPtr _unmanagedResource; private bool _disposed = false;
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) { if (_disposed) return;
if (disposing) { // Libération des ressources managées }
// Libération des ressources non managées if (_unmanagedResource != IntPtr.Zero) { Marshal.FreeHGlobal(_unmanagedResource); _unmanagedResource = IntPtr.Zero; }
_disposed = true; }
~ResourceHolder() { Dispose(false); }}Notez GC.SuppressFinalize(this) — c'est un point clé de l'optimisation. Lorsque vous appelez explicitement Dispose, vous dites au GC : « N'appelle pas le finaliseur, j'ai déjà tout fait ». Cela accélère le garbage collection.
Le pattern Dispose : évolution du simple au complexe
Niveau 1 : Le plus simple (uniquement pour les ressources managées)
Si votre classe utilise uniquement des ressources managées (d'autres objets IDisposable), une implémentation simple suffit :
public class SimpleLogger : IDisposable{ private readonly FileStream _logFile;
public SimpleLogger(string path) { _logFile = new FileStream(path, FileMode.Append); }
public void Dispose() { _logFile?.Dispose(); }}Niveau 2 : Pattern complet (avec finaliseur)
Pour les classes qui travaillent avec des ressources non managées (par exemple, les handles WinAPI), un pattern complet avec finaliseur est nécessaire :
public class SafeHandleWrapper : IDisposable{ private SafeHandle _handle;
public SafeHandleWrapper() { _handle = new SafeFileHandle(IntPtr.Zero, true); }
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) { if (_handle != null && !_handle.IsInvalid) { _handle.Dispose(); } }
// Le finaliseur n'est nécessaire que s'il y a