CodeGym /Cours /C# SELF /Découverte des indexeurs

Découverte des indexeurs

C# SELF
Niveau 18, Leçon 0
Disponible

1. Quelques mots sur les collections

Jusqu'à présent, on a bossé avec des tableaux — c'est la façon la plus simple de stocker plusieurs éléments du même type. Mais en C#, il existe plein de collections plus pratiques et puissantes, qui servent à résoudre différents problèmes.

Une collection, c'est un objet qui peut stocker un groupe d'autres objets. Contrairement aux tableaux, les collections peuvent souvent changer de taille dynamiquement, offrent des méthodes pratiques pour manipuler les données et sont optimisées pour différents scénarios d'utilisation.

Les principaux types de collections (petit aperçu) :

List<T> — un tableau dynamique qui peut grandir ou rétrécir :

List<string> names = new List<string>();
names.Add("Alex");
names.Add("Marie");
Console.WriteLine(names[0]); // Alex
Console.WriteLine(names.Count); // 2

Dictionary<TKey, TValue> — une collection de paires "clé-valeur", où chaque clé correspond à une valeur :

Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Alex"] = 25;
ages["Marie"] = 30;
Console.WriteLine(ages["Alex"]); // 25

Fais gaffe : dans les exemples ci-dessus, on utilise les crochets [] pour accéder aux éléments des collections — exactement comme avec les tableaux ! C'est possible grâce aux indexeurs, dont on va parler aujourd'hui.

C'est juste une première rencontre avec les collections — on verra en détail leurs possibilités, différences et usages dans les prochaines leçons. Pour l'instant, retiens que les collections permettent d'accéder à leurs éléments via des crochets, et que ce n'est pas de la magie, mais un mécanisme spécial du langage.

2. Les indexeurs

Les objets C# classiques fonctionnent via des propriétés et des méthodes. Mais que faire si ton objet est une sorte de mini-collection ? Par exemple, imagine :

  • Tu écris une classe Week, qui doit renvoyer le nom du jour de la semaine par numéro : week[0] → "Lundi".
  • Ou une classe Library, où tu peux accéder à un livre par son numéro : library[3] → "Du côté de chez Swann".

Avoue que c'est pratique, non ? Ce serait bizarre d'écrire library.GetBookByIndex(3) à chaque fois — on veut accéder à notre objet comme à un tableau !

C'est là que les indexeurs entrent en jeu.

Indexeur — c'est un membre spécial d'une classe qui permet d'utiliser les objets de cette classe avec la syntaxe des crochets, comme les tableaux : obj[0], obj["clé"], etc.

Un indexeur ressemble à une propriété, mais au lieu d'un nom, il prend des paramètres entre crochets. C'est un peu comme quand on écrit .Name, sauf qu'ici c'est [i].

3. Une collection simple avec un indexeur

On va se faire une mini-classe qui stocke des couleurs préférées. On utilise un tableau de chaînes pour stocker. Sans indexeur, il faudrait faire une méthode du genre GetColor(int i). Mais avec un indexeur :


using System;

public class FavoriteColors
{
   // Champ privé pour stocker les couleurs
   private string[] colors = new string[5];

   // Indexeur :
   public string this[int index]
   {
       get
       {
           // Contrôle des limites du tableau (vive l'encapsulation !)
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("Indice de couleur invalide !");

           return colors[index];
       }
       set
       {
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("Indice de couleur invalide !");

           colors[index] = value ?? throw new ArgumentNullException(nameof(value));
       }
   }
}

class Program
{
   static void Main()
   {
       FavoriteColors favorites = new FavoriteColors();

       favorites[0] = "Vert";
       favorites[1] = "Bleu";
       favorites[2] = "Rouge";
       favorites[10] = "Violet"; // Lève une exception !

       Console.WriteLine(favorites[1]); // Bleu
   }
}

Qu'est-ce qui se passe ici ?

  • On crée un tableau privé pour que personne ne puisse le bidouiller directement de l'extérieur.
  • L'indexeur est défini comme public string this[int index], où this indique que l'indexeur appartient à l'objet.
  • Dans get et set, on contrôle la plage, on ne laisse pas sortir des limites/écrire null.
  • Au final, on peut faire favorites[0], comme avec un tableau classique.

4. Syntaxe de l'indexeur : détails

La syntaxe ressemble à une propriété, mais au lieu d'un nom (genre Age), on met le mot-clé this avec des paramètres :


// Signature d'un indexeur (modèle général)
[modificateur] TypeRésultat this[TypeIndex nomIndex]
{
   get { ... }
   set { ... }
}

Exemple : Classique

public class MyCollection
{
   private int[] data = new int[10];

   // Indexeur pour lecture et écriture
   public int this[int index]
   {
       get { return data[index]; }
       set { data[index] = value; }
   }
}

Les indexeurs ne sont pas limités à int

Le truc cool : un indexeur n'est pas obligé de prendre juste un int. Tu peux mettre n'importe quel type (tant que l'accès par clé a du sens) :

public string this[string colorName]
{
   get { /* ... */ }
   set { /* ... */ }
}

Par exemple, dans une classe d'annuaire téléphonique, c'est logique de chercher par nom :

public class PhoneBook
{
   private Dictionary<string, int> entries = new Dictionary<string, int>();

   public int this[string name]
   {
       get
       {
           if (entries.ContainsKey(name))
               return entries[name];
           return null;
       }
       set
       {
           entries[name] = value;
       }
   }
}

Je te parlerai des collections et de comment marche Dictionary<string, string> dans les prochaines leçons :P

5. Exemple pratique : Compteur de mots dans un texte

On continue à faire évoluer notre appli. Imaginons qu'on a maintenant une classe qui compte combien de fois chaque mot apparaît dans un texte. C'est pratique si l'utilisateur peut accéder à l'objet via des crochets pour obtenir le nombre par mot :


using System.Collections.Generic;

public class WordCounter
{
   private Dictionary<string, int> counter = new Dictionary<string, int>();

   // Indexeur par chaîne (mot)
   public int this[string word]
   {
       get
       {
           if (counter.ContainsKey(word))
               return counter[word];
           return 0; // Si le mot n'existe pas, on retourne 0.
       }
       set
       {
           counter[word] = value;
       }
   }

   // Méthode pour compter les mots d'une chaîne
   public void AddWords(string text)
   {
       foreach (var word in text.Split(' ', System.StringSplitOptions.RemoveEmptyEntries))
       {
           if (counter.ContainsKey(word))
               counter[word]++;
           else
               counter[word] = 1;
       }
   }
}

// Dans Main :
var wc = new WordCounter();
wc.AddWords("maman lavait la vitre lavait maman papa");
Console.WriteLine($"'maman' apparaît {wc["maman"]} fois");
Console.WriteLine($"'vitre' apparaît {wc["vitre"]} fois");
Console.WriteLine($"'chat' apparaît {wc["chat"]} fois"); // 0

À quoi ça sert en vrai ? Cette approche est souvent utilisée pour créer ses propres collections, des bibliothèques mémoire, des mappings (dicos et index) et même des DSL (langages spécialisés dans C#).

6. Limitations et subtilités

Les indexeurs, c'est puissant, mais il y a quelques règles et pièges (eh ouais, toujours !).

Un indexeur n'a pas de nom

Contrairement à une propriété, un indexeur n'a pas de nom, juste une signature du genre this[type paramètre]. Si tu essaies d'écrire public int MyIndexer[int i] — le compilateur va râler. On utilise seulement this.

Pas d'indexeurs statiques

Les indexeurs sont toujours créés pour une instance de classe, pas pour des membres statiques. Donc tu ne peux pas déclarer static int this[int i] — la logique, c'est que this pointe toujours sur une instance concrète.

Peuvent être surchargés par type/nombre de paramètres

Tu peux créer plusieurs indexeurs dans une même classe, si leurs paramètres diffèrent par type ou nombre. Par exemple :

public string this[int i] { get { ... } set { ... } }
public string this[string key] { get { ... } set { ... } }

C'est légal, le compilateur ne va pas s'y perdre — et il te préviendra si les paramètres se recoupent.

Uniquement via des propriétés

Impossible de déclarer un indexeur sans les accesseurs get ou set. Si tu veux juste en lecture — vire set, juste en écriture — vire get. En général, on met les deux.

7. Utilité pratique et pourquoi c'est important

  • Les indexeurs sont super utilisés dans les collections de données. Beaucoup de classes .NET en ont : par exemple, List<T>, Dictionary<TKey,TValue>. Quand tu écris list[2], tu utilises un indexeur !
  • Les indexeurs permettent de cacher l'implémentation interne (encapsulation !), mais de fournir une interface pratique et familière. L'utilisateur de ta classe ne se demande pas comment tu stockes les données, il utilise juste les crochets [index] comme d'hab.
  • Ton code devient concis et intuitif — ce qui est super apprécié en entretien (et par tes futurs collègues).

Propriétés vs Indexeurs : comparatif

Propriété Indexeur
Nom Oui (par exemple, Name) Non (à la place — this[paramètre])
Accès Par nom Par index (ou autre clé)
Statique Peut être static Seulement pour une instance
Plusieurs dans la classe Oui, autant que tu veux Oui, mais avec des signatures de paramètres différentes
Utilisation Stockage/accès aux données Mini-collections, données associatives

8. Erreurs typiques et conseils

Erreur n°1 : essayer de faire un indexeur static.
Ça marche pas — this[...] fonctionne seulement avec un objet.

Erreur n°2 : oublier de vérifier l'index.
Si tu ne vérifies pas les limites du tableau dans get ou set, ton programme peut planter.

Erreur n°3 : se tromper dans les types de paramètres.
Si tu crées deux indexeurs avec les mêmes paramètres, le compilateur va gueuler.

Erreur n°4 : oublier d'implémenter get ou set.
Si tu veux accès en lecture et écriture — il faut les deux.

Conseil : si ta classe encapsule un tableau — fais juste passer les accès au tableau via l'indexeur. Ça accélère le code et le rend intuitif.

9. Pourquoi tout ça ?

Les indexeurs rendent l'interface d'un objet simple, claire et pratique. Ils permettent de cacher l'implémentation interne, mais de laisser au développeur un moyen facile d'accéder aux données.

C'est comme ça que sont faites les collections intégrées : string, List<T>, Dictionary<TKey, TValue>, Span<T> et plein d'autres. Quand tu écris array[2] ou text[0], tu utilises déjà un indexeur.

Et le plus important — tu peux maintenant écrire tes propres classes qui fonctionnent aussi bien et aussi simplement. Bref, tu fais un pas de plus vers du code pro et lisible.

2
Mission
C# SELF, niveau 18, leçon 0
Bloqué
Création de la classe "Week" avec un indexeur pour les jours de la semaine
Création de la classe "Week" avec un indexeur pour les jours de la semaine
2
Mission
C# SELF, niveau 18, leçon 0
Bloqué
Annuaire téléphonique avec indexeur par nom
Annuaire téléphonique avec indexeur par nom
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION