CodeGym /Cours /C# SELF /Indexeurs avancés en C#: paramètres, modificateurs, fonct...

Indexeurs avancés en C#: paramètres, modificateurs, fonctionnalités modernes

C# SELF
Niveau 18 , Leçon 1
Disponible

1. Indexeurs avec plusieurs paramètres

Un indexeur simple (par exemple, this[int index]) est utile quand tu veux accéder à des éléments par un index entier — un peu comme dans un tableau. Mais C# permet bien plus : tu peux utiliser plusieurs paramètres, des paramètres de types différents, définir des modificateurs d'accès différents pour get et set, et même avoir plusieurs indexeurs dans une même classe (si leurs signatures sont différentes).

Aussi, dans les nouvelles versions de C#, il y a des fonctionnalités en plus qui facilitent la vie avec les indexeurs, y compris du sucre syntaxique pour écrire du code plus concis.

Un indexeur n'est pas obligé de prendre seulement un index et seulement du type int. C'est toi qui définis ses paramètres — tu peux avoir plusieurs paramètres de types différents si ton besoin l'exige.


public class ChessBoard
{
    private string[,] board = new string[8, 8];

    // Indexeur avec deux paramètres !
    public string this[int row, int col]
    {
        get { return board[row, col]; }
        set { board[row, col] = value; }
    }
}
Indexeur avec deux paramètres pour une structure à deux dimensions

Maintenant, on peut écrire du code comme ça :

ChessBoard chess = new ChessBoard();
chess[0, 0] = "Tour";
chess[7, 7] = "Roi";
string piece = chess[0, 0]; // "Tour"

Cette approche est parfaite pour les matrices, les cartes en 2D, les jeux de plateau, les collections complexes.

2. Indexeurs avec des paramètres de types différents

Ton indexeur peut accepter des paramètres non seulement du type int, mais aussi de n'importe quel autre type adapté. L'essentiel, c'est que ça ait du sens pour ton cas d'usage.


using System.Collections.Generic;

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Position { get; set; }
}

public class EmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    // Indexeur par nom d'employé (string)
    public Employee this[string name]
    {
        get
        {
            foreach (var employee in employees)
            {
                if (employee.Name == name)
                    return employee;
            }
            return null; // Ou tu peux lancer une exception
        }
        set
        {
            for (int i = 0; i < employees.Count; i++)
            {
                if (employees[i].Name == name)
                {
                    employees[i] = value;
                    return;
                }
            }
            // Si l'employé avec ce nom n'existe pas — on ajoute un nouveau
            employees.Add(value);
        }
    }

    // Pour la compatibilité — indexeur par index numérique
    public Employee this[int index]
    {
        get { return employees[index]; }
        set { employees[index] = value; }
    }
}
var company = new EmployeeCollection();
company[0] = new Employee { Name = "Ivan", Age = 30, Position = "Programmeur" };
company[1] = new Employee { Name = "Maria", Age = 25, Position = "Designer" };

Employee employee = company["Ivan"];
company["Petr"] = new Employee { Name = "Petr", Age = 28, Position = "Testeur" };

Important : si tu as plusieurs indexeurs — leurs signatures doivent différer par le nombre et le type des paramètres.

3. Modificateurs d'accès différents pour get et set

Parfois, tu veux autoriser seulement la lecture par index, mais pas l'écriture (ou l'inverse). En C#, tu peux définir des modificateurs d'accès différents pour les accesseurs get et set d'un indexeur.


public class SecureEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get { return employees[index]; }
        internal set { employees[index] = value; }
    }
}

C'est souvent utilisé pour protéger les collections contre les modifications non autorisées, rendant la classe plus contrôlable.

4. Indexeurs en lecture seule et écriture seule

Parfois, tu veux autoriser seulement la lecture ou seulement l'écriture via l'indexeur.


public class ReadOnlyEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get { return employees[index]; }
        // set absent — impossible de modifier !
    }
}

public class WriteOnlyEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        set
        {
            employees.Insert(index, value);
        }
        // get absent — impossible de lire !
    }
}

Dans les projets réels, "write-only" est quasi inexistant : en général, on veut du "read-only", par exemple quand on veut juste exposer les données sans permettre de les modifier via l'indexeur.

5. Indexeurs avec vérification des limites et logique

Dans les bons exemples, il est important non seulement de donner l'accès par index, mais aussi de bien gérer les dépassements de limites, les erreurs de recherche et autres situations exceptionnelles.


public class SafeEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get
        {
            if (index < 0 || index >= employees.Count)
                throw new IndexOutOfRangeException("Employé avec cet index n'existe pas !");
            return employees[index];
        }
        set
        {
            if (index < 0 || index >= employees.Count)
                throw new IndexOutOfRangeException("Impossible de remplacer un employé inexistant !");
            employees[index] = value;
        }
    }
}

Tu peux retourner null ou utiliser des patterns modernes pour gérer l'absence de valeur — à toi de voir selon la logique de ton appli.

6. Indexeurs avec des paramètres inhabituels

Tu peux tomber sur des collections où l'indexeur prend des types pas standards : des énumérations (enum), des structs custom, voire plusieurs paramètres de types différents.


public enum Department { IT, RH, Finance, Marketing }

public class DepartmentEmployeeCollection
{
    private Dictionary<Department , Employee> departmentLeads = new Dictionary<Department , Employee>();

    public Employee this[Department  department]
    {
        get { return departmentLeads.TryGetValue(department, out var employee) ? employee : null; }
        set { departmentLeads[department] = value; }
    }
}
var company = new DepartmentEmployeeCollection();
company[Department.IT] = new Employee { Name = "Anna", Age = 35, Position = "Chef IT" };
Employee itLead = company[Department.IT];

Cette approche est utilisée quand tu as des identifiants uniques ou une correspondance claire entre type et valeur — c'est pratique, clair et type-safe.

7. Fonctionnalités modernes : Range et Index

Avec l'arrivée des types Range et Index dans C# 8, les indexeurs ont eu de nouvelles possibilités pour bosser avec les plages et les index à partir de la fin :


public class SmartArray
{
    private int[] numbers = Enumerable.Range(0, 100).ToArray();

    public int[] this[Range range] => numbers[range];
    public int this[Index index] => numbers[index];
}

// Utilisation :
var smart = new SmartArray();
int[] middle = smart[20..30]; // du 20ème au 29ème élément
int last = smart[^1]; // dernier élément

Si tu crées ta propre collection, le support de Range et Index la rend super "native" et agréable à utiliser pour les devs.

8. Propriétés vs indexeurs

Pour mieux piger quand utiliser une propriété ou un indexeur, c'est utile de comparer leurs particularités.

  • Les propriétés sont pratiques pour accéder à des caractéristiques individuelles d'un objet par nom : person.Name, car.Speed.
  • Les indexeurs sont faits pour accéder à des éléments d'une collection ou d'une structure par clé : employees[0], phoneBook["Ivan"].
  • Une propriété a toujours un nom précis et il ne peut y avoir qu'une seule propriété avec ce nom dans la classe.
  • L'indexeur utilise le mot-clé this et peut avoir plusieurs variantes si leurs signatures diffèrent.
  • Les propriétés peuvent être statiques, les indexeurs non, car this pointe toujours sur une instance concrète de l'objet.

9. Erreurs classiques avec les indexeurs

Erreur n°1 : pas de vérification des limites.
Si tu ne vérifies pas l'index pour un dépassement de tableau, tu risques un IndexOutOfRangeException au pire moment.

Erreur n°2 : tu ne gères pas le possible null retourné par l'indexeur.
Si l'indexeur par string retourne null quand l'élément n'existe pas, et que le code appelant utilise le résultat sans réfléchir, tu auras un NullReferenceException.

Erreur n°3 : duplication des signatures d'indexeurs.
C# n'autorise pas deux indexeurs avec les mêmes paramètres.

Erreur n°4 : logique pas claire dans le set accessor.
Si dans le set tu ajoutes au lieu de remplacer, ça peut être déroutant. Ce genre de truc doit être explicite et bien documenté.

10. Application pratique et conclusion

Dans les projets réels, les indexeurs servent souvent à créer des collections spécialisées, des caches, des dictionnaires avec logique supplémentaire, des matrices et des structures de données multidimensionnelles. Ils rendent le code plus lisible et intuitif — au lieu d'appeler des méthodes comme GetElementByIndex(5) tu peux juste écrire collection[5].

Retiens : les indexeurs doivent logiquement correspondre à la nature de ta classe. Si ta classe n'est pas une collection ou une structure de données, probablement qu'elle n'a pas besoin d'indexeur. Mais si ta classe gère un ensemble d'éléments, l'indexeur peut vraiment rendre son utilisation plus pratique et naturelle.

2
Mission
C# SELF, niveau 18, leçon 1
Bloqué
Implémentation d'un indexeur pour un tableau à deux dimensions
Implémentation d'un indexeur pour un tableau à deux dimensions
2
Mission
C# SELF, niveau 18, leçon 1
Bloqué
Indexeur avec énumération (enum)
Indexeur avec énumération (enum)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION