Utiliser des indexeurs (Guide de programmation C#)
Les indexeurs sont une commodité syntaxique qui permet de créer une classe, un struct ou une interface que les applications clientes peuvent accéder comme un tableau. Le compilateur génère une propriété Item
(ou une propriété nommée différemment si IndexerNameAttribute est présent), ainsi que les méthodes d’accesseur appropriées. Le plus souvent, les indexeurs sont implémentés dans les types dont l’objectif premier est d’encapsuler une collection ou un tableau interne. Prenons l’exemple d’une classe TempRecord
qui représente la température, en Fahrenheit, enregistrée à 10 moments différents sur une période de 24 heures. La classe contient un tableau temps
de type float[]
pour stocker les valeurs de température. En implémentant un indexeur dans cette classe, les clients peuvent accéder aux températures dans une instance TempRecord
sous la forme float temp = tempRecord[4]
et non sous la forme float temp = tempRecord.temps[4]
. La notation d’indexeur non seulement simplifie la syntaxe pour les applications clientes, mais elle permet aussi aux autres développeurs de comprendre de façon plus intuitive la classe et son objectif.
Pour déclarer un indexeur sur une classe ou un struct, utilisez le mot clé this, comme dans l’exemple suivant :
// Indexer declaration
public int this[int index]
{
// get and set accessors
}
Important
La déclaration d’un indexeur génère automatiquement une propriété nommée Item
sur l’objet. La propriété Item
n’est pas directement accessible depuis l’expression d’accès au membre de l’instance. De plus, si vous ajoutez votre propre propriété Item
à un objet avec un indexeur, vous obtenez une erreur du compilateur CS0102. Pour éviter cette erreur, utilisez le renommage de l’indexeur IndexerNameAttribute comme détaillé plus loin dans cet article.
Notes
Le type d’un indexeur et le type de ses paramètres doivent être au moins aussi accessibles que l’indexeur lui-même. Pour plus d’informations sur les niveaux d’accessibilité, consultez Modificateurs d’accès.
Pour plus d’informations sur l’utilisation d’indexeurs avec une interface, consultez Indexeurs d’interface.
La signature d’un indexeur est composée du nombre et des types de ses paramètres formels. Elle ne comporte ni le type de l’indexeur ni le nom des paramètres formels. Si vous déclarez plusieurs indexeurs dans la même classe, ils doivent avoir des signatures différentes.
Un indexeur n’est pas classé comme une variable ; par conséquent, une valeur d’indexeur ne peut pas être passée par référence (comme un ref
ou out
paramètre) sauf si sa valeur est une référence (c’est-à-dire, elle est renvoyée par référence).
Pour affecter à l’indexeur un nom exploitable dans d’autres langages, utilisez System.Runtime.CompilerServices.IndexerNameAttribute, comme dans l’exemple suivant :
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}
Cet indexeur a le nom TheItem
, car il est remplacé par l’attribut de nom de l’indexeur. Par défaut, le nom de l’indexeur est Item
.
Exemple 1
L’exemple suivant montre comment déclarer un champ de tableau privé temps
, et un indexeur. L’indexeur permet d’accéder directement à l’instance tempRecord[i]
. Comme alternative à l’utilisation de l’indexeur, vous pouvez déclarer le tableau comme membre public et accéder directement à ses membres, tempRecord.temps[i]
.
public class TempRecord
{
// Array of temperature values
float[] temps =
[
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
];
// To enable client code to validate input
// when accessing your indexer.
public int Length => temps.Length;
// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}
Notez que quand l’accès à un indexeur est évalué, par exemple dans une instruction Console.Write
, l’accesseur get est appelé. C’est pourquoi une erreur de compilation se produit s’il n’existe aucun accesseur get
.
var tempRecord = new TempRecord();
// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}
Indexation avec d’autres valeurs
C# ne limite pas le type de paramètre d’indexeur au type entier. Par exemple, il peut être utile d’utiliser une chaîne de caractères avec un indexeur. Il est possible d’implémenter un tel indexeur en recherchant la chaîne dans la collection et en retournant la valeur appropriée. Comme les accesseurs peuvent être surchargés, les versions string et integer peuvent coexister.
Exemple 2
L’exemple suivant déclare une classe qui stocke les jours de la semaine. Un accesseur get
prend une chaîne, le nom d’un jour, et retourne l’entier correspondant. Par exemple, « Sunday » retourne 0, « Monday » retourne 1 et ainsi de suite.
// Using a string as an indexer value
class DayCollection
{
string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[string day] => FindDayIndex(day);
private int FindDayIndex(string day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
}
}
Exemple de consommation 2
var week = new DayCollection();
Console.WriteLine(week["Fri"]);
try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Exemple 3
L’exemple suivant déclare une classe qui stocke les jours de la semaine à l’aide de l’enum System.DayOfWeek. Un accesseur get
prend un DayOfWeek
, la valeur d’un jour, et retourne l’entier correspondant. Par exemple, DayOfWeek.Sunday
retourne 0, DayOfWeek.Monday
retourne 1, et ainsi de suite.
using Day = System.DayOfWeek;
class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[Day day] => FindDayIndex(day);
private int FindDayIndex(Day day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
}
}
Exemple de consommation 3
var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);
try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Programmation fiable
La sécurité et la fiabilité des indexeurs peuvent être améliorées de deux manières principales :
N’oubliez pas d’incorporer une stratégie de gestion des erreurs au cas où le code client passerait une valeur d’index non valide. Dans le premier exemple plus haut dans cet article, la classe TempRecord fournit une propriété Length qui permet au code client de vérifier l’entrée avant de la passer à l’indexeur. Vous pouvez également placer le code de gestion des erreurs à l’intérieur de l’indexeur lui-même. N’oubliez pas d’indiquer aux utilisateurs toutes les exceptions que vous levez dans un accesseur d’indexeur.
Définissez pour les accesseurs get et set une accessibilité aussi restrictive que possible. Cela est particulièrement important dans le cas de l’accesseur
set
. Pour plus d’informations, consultez Restriction d’accessibilité de l’accesseur.