Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
Les plages et les index fournissent une syntaxe succincte permettant d’accéder à des éléments uniques ou des plages dans une séquence.
Ce didacticiel vous montre comment effectuer les opérations suivantes :
Prise en charge linguistique des index et des plages
Les index et les plages fournissent une syntaxe succincte pour accéder à des éléments uniques ou des plages dans une séquence.
Cette prise en charge du langage s’appuie sur deux nouveaux types et deux nouveaux opérateurs :
- System.Index représente un index dans une séquence.
- Index de l’opérateur
^final, qui spécifie qu’un index est relatif à la fin d’une séquence. - System.Range représente une sous-plage d’une séquence.
-
Opérateur
..de plage, qui spécifie le début et la fin d’une plage en tant qu’opérandes.
Commençons par les règles des index. Considérez un tableau sequence. L’index 0 est le même que sequence[0]. L’index ^0 est le même que sequence[sequence.Length]. L’expression sequence[^0] lève une exception, tout comme sequence[sequence.Length]. Pour n’importe quel nombre n, l’index ^n est identique à sequence.Length - n.
private string[] words = [
// index from start index from end
"first", // 0 ^10
"second", // 1 ^9
"third", // 2 ^8
"fourth", // 3 ^7
"fifth", // 4 ^6
"sixth", // 5 ^5
"seventh", // 6 ^4
"eighth", // 7 ^3
"ninth", // 8 ^2
"tenth" // 9 ^1
]; // 10 (or words.Length) ^0
Vous pouvez récupérer le dernier mot avec l’index ^1 . Ajoutez le code suivant sous l’initialisation :
Console.WriteLine($"The last word is < {words[^1]} >."); // The last word is < tenth >.
Une plage spécifie le début et la fin d’une plage. Le début de la plage est inclusif, mais la fin de la plage est exclusive, ce qui signifie que le début est inclus dans la plage, mais que la fin n’est pas incluse dans la plage. La plage [0..^0] représente l’ensemble de la plage, tout comme [0..sequence.Length] la plage entière.
Le code suivant crée une sous-plage avec les mots « second », « troisième » et « quatrième ». Il inclut de words[1] jusqu'à words[3]. L’élément words[4] n’est pas dans la plage.
string[] secondThirdFourth = words[1..4]; // contains "second", "third" and "fourth"
// < second >< third >< fourth >
foreach (var word in secondThirdFourth)
Console.Write($"< {word} >");
Console.WriteLine();
Le code suivant retourne l'intervalle comprenant « neuvième » et « dixième ». Il inclut words[^2] et words[^1]. L’index words[^0] de fin n’est pas inclus.
string[] lastTwo = words[^2..^0]; // contains "ninth" and "tenth"
// < ninth >< tenth >
foreach (var word in lastTwo)
Console.Write($"< {word} >");
Console.WriteLine();
Les exemples suivants créent des plages ouvertes pour le début, la fin ou les deux :
string[] allWords = words[..]; // contains "first" through "tenth".
string[] firstPhrase = words[..4]; // contains "first" through "fourth"
string[] lastPhrase = words[6..]; // contains "seventh", "eight", "ninth" and "tenth"
// < first >< second >< third >< fourth >< fifth >< sixth >< seventh >< eighth >< ninth >< tenth >
foreach (var word in allWords)
Console.Write($"< {word} >");
Console.WriteLine();
// < first >< second >< third >< fourth >
foreach (var word in firstPhrase)
Console.Write($"< {word} >");
Console.WriteLine();
// < seventh >< eighth >< ninth >< tenth >
foreach (var word in lastPhrase)
Console.Write($"< {word} >");
Console.WriteLine();
Vous pouvez également déclarer des plages ou des index en tant que variables. La variable peut ensuite être utilisée à l’intérieur des caractères [ et ].
Index thirdFromEnd = ^3;
Console.WriteLine($"< {words[thirdFromEnd]} > "); // < eighth >
Range phrase = 1..4;
string[] text = words[phrase];
// < second >< third >< fourth >
foreach (var word in text)
Console.Write($"< {word} >");
Console.WriteLine();
L’exemple suivant montre la plupart des raisons de ces choix. Modifiez x, ypuis z essayez différentes combinaisons. Lorsque vous expérimentez, utilisez des valeurs où x est inférieur à y, et y est inférieur à z pour des combinaisons valides. Ajoutez le code suivant dans une nouvelle méthode. Essayez différentes combinaisons :
int[] numbers = [..Enumerable.Range(0, 100)];
int x = 12;
int y = 25;
int z = 36;
Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}");
Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}");
Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:");
Span<int> x_y = numbers[x..y];
Span<int> y_z = numbers[y..z];
Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]}, numbers[y..z] is {y_z[0]} through {y_z[^1]}");
Console.WriteLine("numbers[x..^x] removes x elements at each end:");
Span<int> x_x = numbers[x..^x];
Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}");
Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]");
Span<int> start_x = numbers[..x];
Span<int> zero_x = numbers[0..x];
Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}");
Span<int> z_end = numbers[z..];
Span<int> z_zero = numbers[z..^0];
Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}");
Non seulement les tableaux ne sont pas les seuls à prendre en charge les indices et les plages. Vous pouvez également utiliser des index et des plages avec des chaînes, Span<T>ou ReadOnlySpan<T>.
Conversions d’expressions d’opérateur de plage implicites
Lorsque vous utilisez la syntaxe d’expression de l’opérateur de plage, le compilateur convertit implicitement les valeurs de début et de fin en une Index et à partir de celles-ci, crée une nouvelle Range instance. Le code suivant montre un exemple de conversion implicite à partir de la syntaxe de l’expression de l’opérateur de plage et son alternative explicite correspondante :
Range implicitRange = 3..^5;
Range explicitRange = new(
start: new Index(value: 3, fromEnd: false),
end: new Index(value: 5, fromEnd: true));
if (implicitRange.Equals(explicitRange))
{
Console.WriteLine(
$"The implicit range '{implicitRange}' equals the explicit range '{explicitRange}'");
}
// Sample output:
// The implicit range '3..^5' equals the explicit range '3..^5'
Important
Les conversions implicites de Int32 en Index déclenchent une exception ArgumentOutOfRangeException lorsque la valeur est négative. De même, le Index constructeur lève un ArgumentOutOfRangeException lorsque le value paramètre est négatif.
Prise en charge des types pour les indices et les plages
Les index et plages fournissent une syntaxe claire et concise pour accéder à un élément unique ou à une plage d’éléments dans une séquence. Une expression d’index retourne généralement le type des éléments d’une séquence. Une expression de plage retourne généralement le même type de séquence que la séquence source.
Tout type qui fournit un indexeur avec un paramètre Index ou Range prend explicitement en charge les index et plages. Un indexeur qui prend un seul Range paramètre peut retourner un type de séquence différent, tel que System.Span<T>.
Important
Les performances du code à l’aide de l’opérateur de plage dépendent du type de l’opérande séquence.
La complexité temporelle de l’opérateur de plage dépend du type de séquence. Par exemple, si la séquence est une string ou un tableau, le résultat est une copie de la section spécifiée de l’entrée, de sorte que la complexité temporelle est O(N) (où N est la longueur de la plage). En revanche, s’il s’agit d’un System.Span<T> ou d’un System.Memory<T>, le résultat fait référence à la même zone de stockage, ce qui implique qu’aucune copie n'est faite et que l’opération est O(1).
En plus de la complexité du temps, cela entraîne des allocations et des copies supplémentaires, ce qui a un impact sur les performances. Dans le code sensible aux performances, envisagez d’utiliser Span<T> ou Memory<T> comme type de séquence, car l’opérateur de plage ne les alloue pas.
Un type est countable s’il a une propriété nommée Length ou Count avec un getter accessible et un type de retour de int. Un type comptable qui ne prend pas explicitement en charge les index ou les plages peut offrir un support implicite pour ces éléments. Pour plus d’informations, consultez les prises en charge dans les sections des index implicites et des plages implicites dans la note de proposition de fonctionnalité. Les plages utilisant la prise en charge implicite de la plage retournent le même type de séquence que la séquence source.
Par exemple, les types .NET suivants prennent en charge les index et les plages : String, Span<T>et ReadOnlySpan<T>. Le List<T> supporte les indices mais ne supporte pas les plages.
Array a un comportement plus nuanceux. Les tableaux de dimensions uniques prennent en charge les index et les plages. Les tableaux multidimensionnels ne prennent pas en charge les indexeurs ou les plages. L’indexeur d’un tableau multidimensionnel a plusieurs paramètres, pas un seul paramètre. Les tableaux jagués, également appelés tableau de tableaux, prennent en charge les plages et les indexeurs. L’exemple suivant montre comment itérer une sous-section rectangulaire d’un tableau en jaquet. Il itère la section dans le centre, à l’exclusion des trois premières et des trois dernières lignes, ainsi que les deux premières et les deux dernières colonnes de chaque ligne sélectionnée :
int[][] jagged =
[
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10,11,12,13,14,15,16,17,18,19],
[20,21,22,23,24,25,26,27,28,29],
[30,31,32,33,34,35,36,37,38,39],
[40,41,42,43,44,45,46,47,48,49],
[50,51,52,53,54,55,56,57,58,59],
[60,61,62,63,64,65,66,67,68,69],
[70,71,72,73,74,75,76,77,78,79],
[80,81,82,83,84,85,86,87,88,89],
[90,91,92,93,94,95,96,97,98,99],
];
var selectedRows = jagged[3..^3];
foreach (var row in selectedRows)
{
var selectedColumns = row[2..^2];
foreach (var cell in selectedColumns)
{
Console.Write($"{cell}, ");
}
Console.WriteLine();
}
Dans tous les cas, l'opérateur de plage pour Array alloue un tableau afin de stocker les éléments générés.
Scénarios pour les index et les plages
Vous utiliserez souvent des plages et des index lorsque vous souhaitez analyser une partie d’une séquence plus grande. La nouvelle syntaxe est plus claire pour lire exactement quelle partie de la séquence est impliquée. La fonction MovingAverage locale prend comme Range argument. La méthode énumère ensuite uniquement cette plage lors du calcul de la valeur minimale, maximale et moyenne. Essayez le code suivant dans votre projet :
int[] sequence = Sequence(1000);
for(int start = 0; start < sequence.Length; start += 100)
{
Range r = start..(start+10);
var (min, max, average) = MovingAverage(sequence, r);
Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}");
}
for (int start = 0; start < sequence.Length; start += 100)
{
Range r = ^(start + 10)..^start;
var (min, max, average) = MovingAverage(sequence, r);
Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}");
}
(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
(
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);
int[] Sequence(int count) => [..Enumerable.Range(0, count).Select(x => (int)(Math.Sqrt(x) * 100))];
Remarque sur les indices de plage et les tableaux
Lorsque vous prenez une plage à partir d’un tableau, le résultat est un tableau copié à partir du tableau initial, plutôt que référencé. La modification des valeurs dans le tableau résultant ne modifie pas les valeurs du tableau initial.
Par exemple:
var arrayOfFiveItems = new[] { 1, 2, 3, 4, 5 };
var firstThreeItems = arrayOfFiveItems[..3]; // contains 1,2,3
firstThreeItems[0] = 11; // now contains 11,2,3
Console.WriteLine(string.Join(",", firstThreeItems));
Console.WriteLine(string.Join(",", arrayOfFiveItems));
// output:
// 11,2,3
// 1,2,3,4,5