1. Pourquoi les collections ont besoin d'interfaces ?
Soyons honnêtes : tant qu'on code avec un type précis (List<int>, Dictionary<string, string>), tout est simple et clair. Mais imagine que tu veux remplacer List<T> par HashSet<T> parce que tu veux que des valeurs uniques. Ou écrire une méthode qui marche avec l'un ou l'autre type de collection. Tu fais comment ?
Pour avoir un point d'entrée unique — il te faut une « prise universelle ». En C#, cette prise c'est les interfaces. Elles définissent un ensemble de règles (méthodes, propriétés) qu'une collection doit avoir. Tout ce qui implémente l'interface — supporte les opérations indiquées.
C'est comme un chargeur de téléphone : si t'as une prise USB-C standard, tu peux charger ton tel, un aspirateur moderne, et même une brosse à dents électrique (faut juste pas les confondre dans le noir).
Exemple concret
Imaginons que tu veux écrire une méthode qui fait la somme de tous les nombres dans n'importe quelle collection de nombres. Voilà à quoi ça ressemblerait sans interfaces :
// Cette méthode marche que pour les tableaux :
static int SommeTableau(int[] tableau)
{
int somme = 0;
for (int i = 0; i < tableau.Length; i++)
{
somme += tableau[i];
}
return somme;
}
// Et celle-là - que pour List<int> :
static int SommeListe(List<int> liste)
{
int somme = 0;
for (int i = 0; i < liste.Count; i++)
somme += liste[i];
return somme;
}
Pas pratique ! Le code est quasi le même. Avec l'interface IEnumerable<int> tu peux faire un truc universel :
static int SommeTout(IEnumerable<int> collection)
{
int somme = 0;
foreach (var nombre in collection)
somme += nombre;
return somme;
}
Et maintenant cette méthode accepte n'importe quel type de collection qui implémente IEnumerable<int> : tableau, liste, set, etc.
2. Les interfaces principales des collections
Dans .NET, il y a quelques interfaces clés pour les collections. On va les voir vite fait — tu verras les détails d'implémentation plus tard dans d'autres confs.
| Interface | À quoi ça sert | Exemples de collections qui l'implémentent |
|---|---|---|
|
Parcourir les éléments (foreach) | Toutes : , , tableaux |
|
Ensemble modifiable (Add, Remove, Count) | , , |
|
Accès par index, modification | , tableaux |
|
Travailler avec des paires "clé-valeur" | , |
|
Set (éléments uniques) | , |
P.S. Pour simplifier, on zappe pour l'instant les « plusieurs niveaux » d'héritage entre ces interfaces. Qui hérite de qui — on verra ça plus tard.
Visualisation : arbre des interfaces de collections
IEnumerable<T>
^
|
ICollection<T>
^ ^
| |
IList<T> ISet<T>
| |
List<T> HashSet<T>
Pour les dictionnaires :
IEnumerable<KeyValuePair<K,V>>
^
|
ICollection<KeyValuePair<K,V>>
^
|
IDictionary<K,V>
|
Dictionary<K,V>
3. Pourquoi utiliser les interfaces dans la vraie vie ?
Flexibilité
Quand ta méthode ou ta classe bosse pas avec un type précis (List<int>), mais avec une interface (IEnumerable<int>), tu peux lui passer n'importe quelle collection qui implémente cette interface. Résultat : ton programme devient flexible et évolutif. (Au lieu de « Seulement iPhone » — maintenant n'importe quel tel avec Bluetooth passe.)
static void AfficheTout(IEnumerable<string> collection)
{
foreach (var item in collection)
Console.WriteLine(item);
}
Maintenant tu peux afficher les éléments de n'importe quelle liste, tableau, set, même le résultat d'une requête LINQ !
Unification
Si tu crées ton propre type de collection (genre une collection super spécifique pour un besoin chelou), implémente les interfaces qu'il faut — et ta collection marchera avec du code externe qui ne la connaît même pas. Voilà pourquoi dans .NET on dit souvent : « code sur les interfaces, pas sur les implémentations concrètes ».
Compatibilité avec les outils standards
Grâce au support des interfaces standards, tes collections peuvent bosser avec LINQ, le tri, la sérialisation et plein d'autres trucs de .NET.
4. List<T> — c'est à la fois une liste, une collection et un énumérable
Deux mots sur l'énumération
IEnumerable<T> — c'est l'interface la plus importante et la plus basique des collections en .NET. Elle définit une seule méthode — GetEnumerator(), qui renvoie un objet énumérateur (Enumerator) pour parcourir la collection. C'est grâce à elle que tu peux écrire :
foreach (var nom in noms)
{
Console.WriteLine(nom);
}
Où noms — c'est une liste, un set, même un tableau. foreach — c'est du sucre syntaxique malin qui sous le capot utilise la méthode GetEnumerator().
L'interface IEnumerable (sans <T>) — c'est l'ancienne version, pas générique. Maintenant, utilise quasi toujours la version générique.
Syntaxe de déclaration de la classe List
Regarde la déclaration de la classe List<T>, si on lève un peu le voile :
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, ...
Ça veut dire qu'un objet List<T> peut être utilisé comme :
- Une liste avec accès par index (IList<T>)
- Une collection où tu peux ajouter/enlever des éléments (ICollection<T>)
- Juste un ensemble d'éléments sur lequel tu peux boucler (IEnumerable<T>)
Exemple :
List<string> noms = new List<string> { "Anna", "Boris", "Vasya" };
// Comme IEnumerable - tu peux boucler dessus
IEnumerable<string> enEnum = noms;
foreach (var n in enEnum) Console.WriteLine(n);
// Comme ICollection - tu peux avoir le Count :
ICollection<string> enCollec = noms;
int count = enCollec.Count;
// Comme IList - tu peux accéder par index :
IList<string> enListe = noms;
string premierItem = enListe[0];
5. Exemple d'appli
Imaginons qu'on a une appli simple de gestion d'utilisateurs et de leurs rôles (on a commencé à la faire dans les confs précédentes) :
// Classe utilisateur
public class Utilisateur
{
public string Nom { get; set; }
public int Age { get; set; }
}
On peut stocker les utilisateurs dans n'importe quelle collection qui implémente IEnumerable<Utilisateur> :
List<Utilisateur> listeUtilisateurs = new List<Utilisateur> { new Utilisateur { Nom = "Anna", Age = 20 } };
HashSet<Utilisateur> setUtilisateurs = new HashSet<Utilisateur> { new Utilisateur { Nom = "Boris", Age = 25 } };
// Méthode qui affiche les noms de tous les utilisateurs
static void AfficheNomsUtilisateurs(IEnumerable<Utilisateur> utilisateurs)
{
foreach (var utilisateur in utilisateurs)
Console.WriteLine(utilisateur.Nom);
}
// Utilisation avec différents types de collections :
AfficheNomsUtilisateurs(listeUtilisateurs);
AfficheNomsUtilisateurs(setUtilisateurs);
Maintenant l'appli est beaucoup plus flexible — sans magie ni surcharges custom !
6. Erreurs classiques et pièges
Une des erreurs les plus courantes — utiliser des types trop précis alors qu'une interface suffit :
// Pas ouf (trop lié à l'implémentation)
void FaitUnTrucAvecListe(List<int> nombres) { ... }
// Mieux (la méthode marche avec n'importe quelle collection)
void FaitUnTrucAvecNombres(IEnumerable<int> nombres) { ... }
Un autre piège — oublier qu'une interface ne donne que les opérations explicitement décrites dedans. Par exemple, dans IEnumerable<T> tu peux pas savoir combien d'éléments il y a (Count), pour ça il faut utiliser ICollection<T> — ou alors les compter à la main.
GO TO FULL VERSION