C# : Le Garbage Collector
Comment le GC nettoie la mémoire automatiquement, les générations, IDisposable, using et les pièges à éviter.
C# : Le Garbage Collector
En C ou C++, le développeur doit libérer la mémoire manuellement (free(), delete). Oublie une libération, c'est une fuite mémoire. Libère deux fois, c'est un crash. En C#, tout ça est automatique grâce au garbage collector (GC).
Si tu n'as pas encore lu l'article sur la mémoire, commence par là - il explique la différence entre stack et heap, qui est essentielle pour comprendre ce que le GC nettoie.
# Comment ça marche
Le GC surveille le heap. La stack se nettoie toute seule (les variables locales disparaissent quand la méthode se termine), mais les objets sur le heap restent tant que quelqu'un les référence.
Quand un objet n'est plus accessible par aucune variable (directement ou indirectement), il est considéré comme "mort" et sera collecté.
void CreerVoiture()
{
Voiture v = new Voiture("Peugeot", "208", 2022);
Console.WriteLine(v.Marque);
} // v sort du scope - plus aucune référence vers l'objet Voiture
// À un moment, le GC récupérera la mémoire occupée par cette Voiturevoid CreerVoiture(){ Voiture v = new Voiture("Peugeot", "208", 2022); Console.WriteLine(v.Marque);} // v sort du scope - plus aucune référence vers l'objet Voiture // À un moment, le GC récupérera la mémoire occupée par cette VoitureLe GC ne s'exécute pas immédiatement après qu'un objet devient inaccessible. Il attend le bon moment (quand la mémoire commence à se remplir) pour minimiser l'impact sur les performances.

# Les générations
Le GC ne parcourt pas tout le heap à chaque nettoyage - ce serait trop lent. Il fonctionne par générations, basé sur une observation simple : la plupart des objets meurent jeunes.
| Génération | Contient | Fréquence de nettoyage |
|---|---|---|
| Gen 0 | Objets nouvellement créés | Très fréquente |
| Gen 1 | Objets ayant survécu à un GC Gen 0 | Modérée |
| Gen 2 | Objets à longue durée de vie | Rare |
Le cycle de vie d'un objet
- Un objet est créé -> il va en Gen 0
- Le GC nettoie la Gen 0 -> si l'objet est encore référencé, il est promu en Gen 1
- Le GC nettoie la Gen 1 -> s'il survit encore, il passe en Gen 2
- La Gen 2 n'est nettoyée que rarement (c'est coûteux car elle contient beaucoup d'objets)
Les variables temporaires, résultats intermédiaires et objets éphémères meurent en Gen 0 - nettoyage rapide, peu d'impact. Les caches, singletons et objets applicatifs vivent en Gen 2 - rarement touchés.
# Ce que le GC ne fait PAS
Le GC libère la mémoire, mais il ne gère pas les ressources externes :
- Fichiers ouverts
- Connexions réseau ou base de données
- Handles système (sockets, threads natifs...)
Si tu crées un objet qui ouvre un fichier et que tu attends le GC pour le fermer, le fichier peut rester verrouillé pendant un temps indéterminé. C'est là qu'interviennent IDisposable et using.
# IDisposable et using
Le pattern IDisposable
Les classes qui gèrent des ressources externes implémentent l'interface IDisposable. Elle expose une seule méthode : Dispose(), qui libère les ressources proprement.
StreamReader reader = new StreamReader("fichier.txt");
string contenu = reader.ReadToEnd();
reader.Dispose(); // ferme le fichier explicitementStreamReader reader = new StreamReader("fichier.txt");string contenu = reader.ReadToEnd();reader.Dispose(); // ferme le fichier explicitementLe problème : si une exception est levée avant Dispose(), le fichier n'est jamais fermé.
using - la solution
using garantit que Dispose() est appelé quoi qu'il arrive, même en cas d'exception. C'est l'équivalent d'un try/finally avec Dispose() dans le finally.
// Syntaxe classique (bloc)
using (StreamReader reader = new StreamReader("fichier.txt"))
{
string contenu = reader.ReadToEnd();
} // reader.Dispose() est appelé automatiquement ici
// Syntaxe simplifiée (C# 8+) - Dispose à la fin du scope
using StreamReader reader = new StreamReader("fichier.txt");
string contenu = reader.ReadToEnd();
// reader.Dispose() est appelé à la fin de la méthode// Syntaxe classique (bloc)using (StreamReader reader = new StreamReader("fichier.txt")){ string contenu = reader.ReadToEnd();} // reader.Dispose() est appelé automatiquement ici // Syntaxe simplifiée (C# 8+) - Dispose à la fin du scopeusing StreamReader reader = new StreamReader("fichier.txt");string contenu = reader.ReadToEnd();// reader.Dispose() est appelé à la fin de la méthode Règle simple : si une classe implémente IDisposable, utilise toujours using. Ne compte jamais sur le GC pour fermer des fichiers ou des connexions.
# Les pièges courants
N'appelle jamais GC.Collect()
// Ne fais JAMAIS ça
GC.Collect();// Ne fais JAMAIS çaGC.Collect();Le GC est optimisé pour choisir le bon moment. Forcer une collection dégrade les performances car ça force un nettoyage complet, potentiellement sur les 3 générations. Dans 99.9% des cas, le GC fait un meilleur travail que toi.
Attention aux références cachées
Un objet n'est collecté que s'il est totalement inaccessible. Des références cachées peuvent le maintenir en vie :
// Piège : l'event handler garde une référence vers l'objet
button.Click += maMethode;
// Même si tu n'utilises plus l'objet, le bouton le maintient en vie
// Solution : se désabonner quand on n'en a plus besoin
button.Click -= maMethode;// Piège : l'event handler garde une référence vers l'objetbutton.Click += maMethode;// Même si tu n'utilises plus l'objet, le bouton le maintient en vie// Solution : se désabonner quand on n'en a plus besoinbutton.Click -= maMethode;Les objets statiques vivent éternellement
static List<Voiture> historique = new List<Voiture>();
void TraiterVoiture(Voiture v)
{
historique.Add(v); // v ne sera JAMAIS collectée
}static List<Voiture> historique = new List<Voiture>(); void TraiterVoiture(Voiture v){ historique.Add(v); // v ne sera JAMAIS collectée}Les champs static vivent aussi longtemps que l'application. Si tu stockes des objets dans une collection statique sans jamais les retirer, c'est une fuite mémoire.
# La suite
Tu comprends maintenant comment C# gère le cycle de vie des objets en mémoire. Les prochains articles de la série :
- Les interfaces, structs, records & enums
- C# vs .NET - ce qui est du langage et ce qui est du framework