Pourquoi hériter de List<T> en C# est une mauvaise idée : analyse

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : le piège dans lequel beaucoup sont tombés

\n

Imaginez la situation : vous écrivez une application pour une bibliothèque en ligne. Vous avez besoin de votre propre classe Library, qui stocke des livres, sait les trier, les rechercher et afficher des statistiques. La première chose qui vient à l'esprit est d'hériter de List<Book>. Après tout, il a déjà tout : Add, Remove, l'indexation, la recherche. Logique ? Il semblerait que oui.

\n

Mais les développeurs C# expérimentés vous diront : « N'héritez jamais de List<T> ». Cette question sur Stack Overflow a recueilli des milliers de vues, et ce n'est pas pour rien. Derrière la simplicité apparente se cachent de sérieux problèmes d'architecture, de performance et, surtout, de violation des principes de conception orientée objet.

\n

Dans cet article, nous allons regarder sous le capot de List<T>, comprendre pourquoi en hériter est un anti-patron de conception, et quoi utiliser à la place. Prêts ? C'est parti !

\n\n

Qu'est-ce qui ne va pas avec List<T> du point de vue de la conception ?

\n

Commençons par l'essentiel : List<T> n'a pas été conçu comme une base pour une hiérarchie, mais comme une implémentation concrète et hautes performances d'un tableau dynamique. Son API publique est un ensemble de méthodes pour travailler avec une collection, et non un contrat pour l'extension.

\n

Regardez sa déclaration dans les sources de .NET (simplifiée) :

\n
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable\n{\n    private T[] _items;\n    private int _size;\n    \n    public void Add(T item) { /* ... */ }\n    public bool Remove(T item) { /* ... */ }\n    // ... des dizaines d'autres méthodes\n}
\n

Notez que List<T> n'est pas marqué comme abstract. Il ne fournit pas de méthodes virtuelles à redéfinir (à l'exception de quelques-unes, dont nous parlerons plus tard). Cela signifie que vous ne pouvez pas modifier son comportement en toute sécurité. Si vous redéfinissez Add, vous risquez de casser la logique interne qui dépend de cette méthode.

\n\n

Problème n°1 : Violation du principe de substitution de Liskov (LSP)

\n

Le principe de substitution de Liskov stipule : « Une classe héritière doit compléter, et non remplacer, le comportement de l'ancêtre ». Lorsque vous héritez de List<T>, vous promettez que votre classe peut être utilisée partout où un List<T> est attendu. Mais cela est rarement respecté.

\n

Exemple : vous créez une classe ReadOnlyLibrary : List<Book> et redéfinissez Add pour qu'il lève une exception :

\n
public class ReadOnlyLibrary : List<Book>\n{\n    public new void Add(Book book)\n    {\n        throw new InvalidOperationException("Collection en lecture seule !");\n    }\n}\n\n// Quelque part dans le code :\nList<Book> list = new ReadOnlyLibrary();\nlist.Add(new Book()); // Oups ! Aucune exception ne sera levée !\n// Parce que new ne redéfinit pas, mais masque la méthode.\n// C'est List.Add qui sera appelé, pas le nôtre.
\n

Même en utilisant override (et List.Add n'est pas virtuel), vous ne pouvez pas interdire l'ajout — l'interface IList ne le prévoit pas. Vous brisez simplement le contrat.

\n\n

Problème n°2 : Violation de l'encapsulation

\n

En héritant de List<T>, vous exposez toute son API publique vers l'extérieur. Votre classe Library héritera des méthodes Sort, BinarySearch, Reverse, Insert, etc. Mais que faire si vous voulez que les livres soient toujours triés par auteur ? L'utilisateur de votre classe pourra appeler library.Reverse() et briser l'invariant.

\n

Vous ne pourrez pas l'empêcher sans réécrire toutes les méthodes — et il y en a des dizaines. C'est une violation de l'encapsulation : vous exposez une implémentation interne que vous ne pouvez pas contrôler.

\n\n

Regardons sous le capot : comment List<T> est structuré à l'intérieur

\n

Pour comp

Blogs

Book Recommendations