Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Vous définissez des indexeurs lorsque des instances d’une classe ou d’un struct peuvent être indexées comme un tableau ou une autre collection. La valeur indexée peut être définie ou récupérée sans spécifier explicitement un type ou un membre d’instance. Les indexeurs ressemblent à des propriétés , sauf que leurs accesseurs prennent des paramètres.
L’exemple suivant définit une classe générique avec des méthodes d’accesseur get
et set
pour attribuer et récupérer des valeurs.
namespace Indexers;
public class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
// Define the indexer to allow client code to use [] notation.
public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}
L’exemple précédent montre un indexeur en lecture/écriture. Il contient à la fois l'accesseur get
et l'accesseur set
. Vous pouvez définir des indexeurs en lecture seule en tant que membre défini par une expression, comme illustré dans les exemples suivants :
namespace Indexers;
public class ReadOnlySampleCollection<T>(params IEnumerable<T> items)
{
// Declare an array to store the data elements.
private T[] arr = [.. items];
public T this[int i] => arr[i];
}
Le get
mot clé n’est pas utilisé ; =>
introduit le corps de l’expression.
Les indexeurs activent les propriétés indexées : propriétés référencées à l’aide d’un ou plusieurs arguments. Ces arguments fournissent un index dans une collection de valeurs.
- Les indexeurs permettent aux objets d’être indexés comme les tableaux.
- Un
get
accesseur retourne une valeur. Unset
accesseur attribue une valeur. - Le
this
mot clé définit l’indexeur. - Le mot clé
value
est l’argument de l’accesseurset
. - Les indexeurs ne nécessitent pas de valeur d’index entier ; c’est à vous de définir le mécanisme de recherche spécifique.
- Les indexeurs peuvent être surchargés.
- Les indexeurs peuvent avoir un ou plusieurs paramètres formels, par exemple, lors de l’accès à un tableau à deux dimensions.
- Vous pouvez déclarer des indexeurs
partial
dans des typespartial
.
Vous pouvez appliquer presque tout ce que vous avez appris de l’utilisation des propriétés aux indexeurs. La seule exception à cette règle est les propriétés automatiquement implémentées. Le compilateur ne peut pas toujours générer le stockage correct pour un indexeur. Vous pouvez définir plusieurs indexeurs sur un type, tant que les listes d’arguments pour chaque indexeur sont uniques.
Utilisations d’indexeurs
Vous définissez des indexeurs dans votre type lorsque son API modélise une collection. Votre indexeur n'a pas besoin de mapper directement aux types de collection qui font partie du .NET Core. Les indexeurs vous permettent de fournir l’API qui correspond à l’abstraction de votre type sans exposer les détails internes de la façon dont les valeurs de cette abstraction sont stockées ou calculées.
Tableaux et vecteurs
Votre type peut modéliser un tableau ou un vecteur. L’avantage de créer votre propre indexeur est que vous pouvez définir le stockage de cette collection en fonction de vos besoins. Imaginez un scénario dans lequel vos types modélisent des données historiques trop volumineuses pour être chargées en mémoire à la fois. Vous devez charger et décharger des sections de la collection en fonction de l’utilisation. L’exemple suivant modélise ce comportement. Il signale le nombre de points de données qui existent. Il crée des pages pour contenir des sections des données à la demande. Il supprime les pages de la mémoire pour rendre la place pour les pages nécessaires aux demandes plus récentes.
namespace Indexers;
public record Measurements(double HiTemp, double LoTemp, double AirPressure);
public class DataSamples
{
private class Page
{
private readonly List<Measurements> pageData = new ();
private readonly int _startingIndex;
private readonly int _length;
public Page(int startingIndex, int length)
{
_startingIndex = startingIndex;
_length = length;
// This stays as random stuff:
var generator = new Random();
for (int i = 0; i < length; i++)
{
var m = new Measurements(HiTemp: generator.Next(50, 95),
LoTemp: generator.Next(12, 49),
AirPressure: 28.0 + generator.NextDouble() * 4
);
pageData.Add(m);
}
}
public bool HasItem(int index) =>
((index >= _startingIndex) &&
(index < _startingIndex + _length));
public Measurements this[int index]
{
get
{
LastAccess = DateTime.Now;
return pageData[index - _startingIndex];
}
set
{
pageData[index - _startingIndex] = value;
Dirty = true;
LastAccess = DateTime.Now;
}
}
public bool Dirty { get; private set; } = false;
public DateTime LastAccess { get; set; } = DateTime.Now;
}
private readonly int _totalSize;
private readonly List<Page> pagesInMemory = new ();
public DataSamples(int totalSize)
{
this._totalSize = totalSize;
}
public Measurements this[int index]
{
get
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
return page[index];
}
set
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
page[index] = value;
}
}
private Page updateCachedPagesForAccess(int index)
{
foreach (var p in pagesInMemory)
{
if (p.HasItem(index))
{
return p;
}
}
var startingIndex = (index / 1000) * 1000;
var newPage = new Page(startingIndex, 1000);
addPageToCache(newPage);
return newPage;
}
private void addPageToCache(Page p)
{
if (pagesInMemory.Count > 4)
{
// remove oldest non-dirty page:
var oldest = pagesInMemory
.Where(page => !page.Dirty)
.OrderBy(page => page.LastAccess)
.FirstOrDefault();
// Note that this may keep more than 5 pages in memory
// if too much is dirty
if (oldest != null)
pagesInMemory.Remove(oldest);
}
pagesInMemory.Add(p);
}
}
Vous pouvez suivre cette idiome de conception pour modéliser n’importe quel type de collection où il existe de bonnes raisons de ne pas charger l’ensemble des données dans une collection en mémoire. Notez que la Page
classe est une classe imbriquée privée qui ne fait pas partie de l’interface publique. Ces détails sont masqués par les utilisateurs de cette classe.
Dictionnaires
Un autre scénario courant est le cas où vous devez modéliser un dictionnaire ou une carte. Dans ce cas, votre type stocke des valeurs en fonction des clés, potentiellement des clés de texte. Cet exemple crée un dictionnaire qui mappe des arguments de ligne de commande aux expressions lambda qui gèrent ces options. L’exemple suivant montre deux classes : une ArgsActions
classe qui associe une option de ligne de commande à un System.Action délégué, et une ArgsProcessor
qui utilise le ArgsActions
pour exécuter chaque Action lorsqu'elle rencontre cette option.
namespace Indexers;
public class ArgsProcessor
{
private readonly ArgsActions _actions;
public ArgsProcessor(ArgsActions actions)
{
_actions = actions;
}
public void Process(string[] args)
{
foreach (var arg in args)
{
_actions[arg]?.Invoke();
}
}
}
public class ArgsActions
{
readonly private Dictionary<string, Action> _argsActions = new();
public Action this[string s]
{
get
{
Action? action;
Action defaultAction = () => { };
return _argsActions.TryGetValue(s, out action) ? action : defaultAction;
}
}
public void SetOption(string s, Action a)
{
_argsActions[s] = a;
}
}
Dans cet exemple, la ArgsAction
collection est mappée étroitement à la collection sous-jacente. Détermine get
si une option donnée est configurée. Si c’est le cas, elle retourne l’option Action associée. Si ce n’est pas le cas, elle retourne une Action valeur qui ne fait rien. L’accesseur public n’inclut pas d’accesseur set
, Au lieu de cela, la conception utilise une méthode publique pour définir des options.
Cartes multidimensionnelles
Vous pouvez créer des indexeurs qui utilisent plusieurs arguments. En outre, ces arguments ne sont pas contraints d’être du même type.
L’exemple suivant montre une classe qui génère des valeurs pour un ensemble de Mandelbrot. Pour plus d’informations sur les mathématiques derrière l’ensemble, lisez cet article. L’indexeur utilise deux doubles pour définir un point dans le plan X, Y. L’accesseur get
calcule le nombre d’itérations jusqu’à ce qu’un point soit déterminé à ne pas appartenir à l'ensemble. Lorsque le nombre maximal d'itérations est atteint, le point se trouve dans l'ensemble et la valeur de maxIterations de la classe est renvoyée. (Les images générées par l’ordinateur popularisées pour l’ensemble De Mandelbrot définissent des couleurs pour le nombre d’itérations nécessaires pour déterminer qu’un point est en dehors de l’ensemble.)
namespace Indexers;
public class Mandelbrot(int maxIterations)
{
public int this[double x, double y]
{
get
{
var iterations = 0;
var x0 = x;
var y0 = y;
while ((x * x + y * y < 4) &&
(iterations < maxIterations))
{
(x, y) = (x * x - y * y + x0, 2 * x * y + y0);
iterations++;
}
return iterations;
}
}
}
L’ensemble De Mandelbrot définit des valeurs à chaque coordonnée (x,y) pour les valeurs numériques réelles. Cela définit un dictionnaire qui peut contenir un nombre infini de valeurs. Par conséquent, il n’y a pas d’espace de rangement derrière le set. Au lieu de cela, cette classe calcule la valeur de chaque point lorsque le code appelle l’accesseur get
. Aucun stockage sous-jacent n’est utilisé.
Résumé
Vous créez des indexeurs chaque fois que vous avez un élément de type propriété dans votre classe où cette propriété ne représente pas une seule valeur, mais plutôt un ensemble de valeurs. Un ou plusieurs arguments identifient chaque élément individuel. Ces arguments peuvent identifier de manière unique l’élément du jeu à référencer. Les indexeurs étendent le concept de propriétés, où un membre est traité comme un élément de données extérieur à la classe, mais comme une méthode à l’intérieur. Les indexeurs permettent aux arguments de trouver un élément unique dans une propriété qui représente un ensemble d’éléments.
Vous pouvez accéder à l’exemple de dossier pour les indexeurs. Pour obtenir des instructions de téléchargement, consultez Exemples et didacticiels.
Spécification du langage C#
Pour plus d’informations, consultez Indexeurs dans la spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.