CodeGym /Cours /C# SELF /Contrat d’énumérabilité : IEnumerable<T>

Contrat d’énumérabilité : IEnumerable<T>

C# SELF
Niveau 28, Leçon 1
Disponible

1. Intro

Comme t’as sûrement déjà remarqué, les différentes collections C# ont des points communs. Par exemple, tu peux quasiment toutes les parcourir avec une boucle foreach :


var noms = new List<string> { "Ania", "Boris", "Vika" };

// Attention — ce code marche avec List, un tableau, même un HashSet !
foreach (var nom in noms)
{
    Console.WriteLine(nom);
}

C’est quoi la magie ? Tout vient du fait que toutes les collections modernes implémentent l’interface IEnumerable<T> — une sorte de "contrat" qui garantit que la collection sait renvoyer ses éléments un par un, dans l’ordre.

Imagine une boîte où, pour n’importe quelle collection d’employés (département, équipe projet, liste des gens à la soirée), il y a une règle : si l’objet implémente l’interface "IEnumerable", alors tu peux toujours parcourir tous les employés dans un certain ordre, peu importe "comment ils sont stockés", l’important c’est que tu puisses les énumérer.

2. Interface IEnumerable<T> — c’est quoi dedans ?

Regarde à quoi ressemble cette interface dans .NET :


public interface IEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();
}

Il n’y a qu’une seule méthode — GetEnumerator, qui renvoie un objet du type IEnumerator<T>. Cet objet gère le process d’"énumération" : il sait quel élément est courant, comment passer au suivant, quand c’est fini.

En bref : si une classe (ou une collection) implémente IEnumerable<T>, tu peux la parcourir avec une boucle foreach, choper les éléments un par un — et peu importe comment c’est fait à l’intérieur : liste, hash-table, même sur le disque dur.

Mini-schéma


Collection (genre List<T>)
    |
    v
IEnumerable<T>
    |
    v
IEnumerator<T> (le vrai "parcourreur" d’éléments)

3. Pratique et universalité du code

Le gros avantage de ce contrat — c’est l’universalité. Si une fonction ou une méthode prend en paramètre un IEnumerable<T>, ça veut dire qu’elle est compatible avec n’importe quel type de collection — tableaux, listes, hashsets, queues, même tes propres collections custom !

Par exemple, regarde une fonction qui fait la somme des éléments :


// On peut additionner n’importe quel ensemble de int qui supporte IEnumerable<int>
int Somme(IEnumerable<int> nombres)
{
    int resultat = 0;
    foreach (var n in nombres)
        resultat += n;
    return resultat;
}

// Ça marche avec List<int>
var liste = new List<int> { 1, 2, 3 };
Console.WriteLine(Somme(liste));

// Ça marche avec un tableau !
int[] tableau = { 4, 5, 6 };
Console.WriteLine(Somme(tableau));

// Ça marche même avec le résultat d’une méthode qui filtre les éléments
Console.WriteLine(Somme(liste.Where(x => x % 2 == 0))); // On utilise LINQ

Exemple IRL : méthodes universelles

Imagine que tu dois écrire un utilitaire pour trouver le mot le plus long dans n’importe quelle collection de chaînes. Grâce à IEnumerable<string>, tu peux le faire pour n’importe quelle source — tableau, liste, résultat de méthodes de filtrage, etc. :


string TrouveLePlusLong(IEnumerable<string> mots)
{
    string plusLong = "";
    foreach (var mot in mots)
        if (mot.Length > plusLong.Length)
            plusLong = mot;
    return plusLong;
}

Tu peux l’utiliser avec n’importe quel ensemble adapté.

4. Pourquoi la boucle foreach marche avec IEnumerable<T> ?

Question fréquente : pourquoi la boucle foreach "comprend" toutes les collections ? La réponse est simple : le compilateur C# cherche dans la classe une méthode GetEnumerator et s’attend à recevoir un objet avec les méthodes MoveNext() et la propriété Current. C’est ça l’interface standard — IEnumerator<T>.

Grâce à ce système, tu peux même écrire ta propre classe qui "sort" les éléments à la suite (genre un générateur de suite de Fibonacci), et si elle implémente IEnumerable<int>, tu peux l’utiliser dans foreach comme une liste normale.

5. C’est quoi un Enumerator et comment il marche ?

Dans chaque collection qui implémente IEnumerable<T>, il y a un "parcourreur" spécial — Enumerator (ou plus officiellement : un objet qui implémente l’interface IEnumerator<T>). C’est lui qui "traîne" les éléments de la collection un par un quand tu fais une boucle foreach.

Interface IEnumerator<T>

Voilà ce que sait faire un Enumerator standard :


public interface IEnumerator<T> : IDisposable
{
    T Current { get; }         // Élément courant
    bool MoveNext();           // Aller à l’élément suivant
    void Reset();              // Revenir au début (rarement utilisé)
}

Comment ça marche à l’intérieur ?

  • MoveNext() — déplace le "curseur" vers l’élément suivant et renvoie true s’il y a un élément. S’il n’y a plus d’éléments — renvoie false.
  • Current — donne l’élément courant (celui sur lequel le parcourreur pointe).
  • Reset() — remet l’Enumerator au début (mais on s’en sert quasi jamais).
  • Dispose() — libère les ressources (utile pour les collections qui bossent avec des fichiers ou le réseau).

Exemple : comment foreach marche "sous le capot"

Quand tu écris :


var nombres = new List<int> { 1, 2, 3 };
foreach (var n in nombres)
    Console.WriteLine(n);

En vrai, le compilateur transforme ça en un truc du genre :


var nombres = new List<int> { 1, 2, 3 };

// On chope le "parcourreur"
var enumerateur = nombres.GetEnumerator();
while (enumerateur.MoveNext())
{
    var n = enumerateur.Current;
    Console.WriteLine(n);
}
// Le compilateur appelle automatiquement Dispose() dans un bloc using (si enumerator implémente IDisposable)

Important : si la collection bosse avec des ressources externes (genre fichiers, bases de données), l’Enumerator peut les libérer automatiquement à la fin du parcours.

Schéma visuel


Début du parcours -> GetEnumerator() -> Enumerator
         |
         v
  MoveNext() -> Current
         |
         v
  MoveNext() -> Current
         |
        ...
         |
         v
  MoveNext() == false -> fin du parcours

6. IEnumerable et tableaux, listes, ensembles : qui est parent avec qui

Regarde quels conteneurs standards .NET implémentent cette interface :

Type de collection Implémente IEnumerable<T> ? On peut parcourir avec foreach ?
Tableau (
int[]
)
✅ ✅
List<T>
✅ ✅
Dictionary<TKey, V>
✅ (par paires, clés, valeurs) ✅
HashSet<T>
✅ ✅
Queue<T>
✅ ✅
Stack<T>
✅ ✅

Même une chaîne (string) implémente le IEnumerable classique, donc tu peux aussi parcourir chaque caractère d’une string.

7. Implémenter son propre Enumerable

Petit défi : essayons de coder une collection minimale qui stocke les nombres pairs de 0 à N et implémente IEnumerable<int>. Comme ça, on pourra la parcourir en boucle et l’utiliser avec LINQ.


// Classe-collection qu’on peut "parcourir"
class NombresPairs : IEnumerable<int>
{
    private int max;

    public NombresPairs(int max)
    {
        this.max = max;
    }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i <= max; i += 2)
            yield return i; // Petite magie pour faire un enumerator
    }

    // Implémentation explicite de l’interface IEnumerable non générique pour la compatibilité
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// Utilisation :
var pairs = new NombresPairs(10);
foreach(var e in pairs)
    Console.Write($"{e} "); // 0 2 4 6 8 10

Note le truc clé : si ta classe implémente IEnumerable<T> — elle devient direct compatible avec la plupart des outils .NET : LINQ, foreach, méthodes qui prennent un enumerable.

8. Pièges classiques et subtilités

Parfois, les débutants pensent que IEnumerable<T> — c’est une collection à part entière, qui a des éléments. En vrai — c’est juste une "promesse" que si tu commences à parcourir, on va te "donner" les éléments un par un.

Si tu veux accéder à un élément par index (maListe[5]), utiliser des méthodes genre Add ou Remove — l’interface IEnumerable<T> ne sert à rien ici. C’est juste pour le parcours séquentiel !

Erreur : essayer de modifier une collection pendant qu’on la parcourt. Par exemple :


foreach (var item in maListe)
{
    if (item < 0)
        maListe.Remove(item); // DANGER ! InvalidOperationException
}

Mieux vaut d’abord faire une liste séparée pour supprimer, ou utiliser des méthodes qui créent une nouvelle collection :


// Façon safe — on crée une nouvelle collection à la main
var nouvelleListe = new List<int>();
foreach (var item in maListe)
{
    if (item >= 0)
        nouvelleListe.Add(item);
}
maListe = nouvelleListe;

// Ou on supprime par index en partant de la fin
for (int i = maListe.Count - 1; i >= 0; i--)
{
    if (maListe[i] < 0)
        maListe.RemoveAt(i);
}
2
Mission
C# SELF, niveau 28, leçon 1
Bloqué
Implémentation d'une collection personnalisée prenant en charge IEnumerable
Implémentation d'une collection personnalisée prenant en charge IEnumerable
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION