Índices y rangosIndices and ranges

Los intervalos e índices proporcionan una sintaxis concisa para acceder a elementos únicos o intervalos en una secuencia.Ranges and indices provide a succinct syntax for accessing single elements or ranges in a sequence.

En este tutorial aprenderá lo siguiente:In this tutorial, you'll learn how to:

  • Usar la sintaxis para intervalos de una secuencia.Use the syntax for ranges in a sequence.
  • Comprender las decisiones de diseño para iniciar y finalizar cada secuencia.Understand the design decisions for the start and end of each sequence.
  • Descubrir escenarios para los tipos Index y Range.Learn scenarios for the Index and Range types.

Compatibilidad con idiomas para los índices y los rangosLanguage support for indices and ranges

Esta compatibilidad con lenguajes se basa en dos nuevos tipos y dos nuevos operadores:This language support relies on two new types and two new operators:

  • System.Index representa un índice en una secuencia.System.Index represents an index into a sequence.
  • Índice desde el operador final ^, que especifica que un índice es relativo al final de una secuencia.The index from end operator ^, which specifies that an index is relative to the end of a sequence.
  • System.Range representa un subrango de una secuencia.System.Range represents a sub range of a sequence.
  • El operador de intervalo .., que especifica el inicio y el final de un intervalo como sus operandos.The range operator .., which specifies the start and end of a range as its operands.

Comencemos con las reglas de los índices.Let's start with the rules for indices. Considere un elemento sequence de matriz.Consider an array sequence. El índice 0 es igual que sequence[0].The 0 index is the same as sequence[0]. El índice ^0 es igual que sequence[sequence.Length].The ^0 index is the same as sequence[sequence.Length]. La expresión sequence[^0] produce una excepción, al igual que sequence[sequence.Length].The expression sequence[^0] does throw an exception, just as sequence[sequence.Length] does. Para cualquier número n, el índice ^n es igual que sequence[sequence.Length - n].For any number n, the index ^n is the same as sequence[sequence.Length - n].

string[] words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

Puede recuperar la última palabra con el índice ^1.You can retrieve the last word with the ^1 index. Agregue el código siguiente a la inicialización:Add the following code below the initialization:

Console.WriteLine($"The last word is {words[^1]}");

Un rango especifica el inicio y el final de un intervalo.A range specifies the start and end of a range. Los rangos son excluyentes, lo que significa que el final no se incluye en el intervalo.Ranges are exclusive, meaning the end isn't included in the range. El rango [0..^0] representa todo el intervalo, al igual que [0..sequence.Length] representa todo el intervalo.The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

El siguiente código crea un subrango con las palabras "quick", "brown" y "fox".The following code creates a subrange with the words "quick", "brown", and "fox". Va de words[1] a words[3].It includes words[1] through words[3]. El elemento words[4] no se encuentra en el intervalo.The element words[4] isn't in the range. Agregue el código siguiente al mismo método.Add the following code to the same method. Cópielo y péguelo en la parte inferior de la ventana interactiva.Copy and paste it at the bottom of the interactive window.

string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
    Console.Write($"< {word} >");
Console.WriteLine();

El código siguiente devuelve el rango con "lazy" y "dog".The following code returns the range with "lazy" and "dog". Incluye words[^2] y words[^1].It includes words[^2] and words[^1]. El índice del final words[^0] no se incluye.The end index words[^0] isn't included. Agregue el código siguiente también:Add the following code as well:

string[] lazyDog = words[^2..^0];
foreach (var word in lazyDog)
    Console.Write($"< {word} >");
Console.WriteLine();

En los ejemplos siguientes se crean rangos con final abierto para el inicio, el final o ambos:The following examples create ranges that are open ended for the start, end, or both:

string[] allWords = words[..]; // contains "The" through "dog".
string[] firstPhrase = words[..4]; // contains "The" through "fox"
string[] lastPhrase = words[6..]; // contains "the, "lazy" and "dog"
foreach (var word in allWords)
    Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in firstPhrase)
    Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in lastPhrase)
    Console.Write($"< {word} >");
Console.WriteLine();

También puede declarar rangos o índices como variables.You can also declare ranges or indices as variables. La variable se puede usar luego dentro de los caracteres [ y ]:The variable can then be used inside the [ and ] characters:

Index the = ^3;
Console.WriteLine(words[the]);
Range phrase = 1..4;
string[] text = words[phrase];
foreach (var word in text)
    Console.Write($"< {word} >");
Console.WriteLine();

El ejemplo siguiente muestra muchos de los motivos para esas opciones.The following sample shows many of the reasons for those choices. Modifique x, y y z para probar diferentes combinaciones.Modify x, y, and z to try different combinations. Al experimentar, use valores donde x sea menor que y y y sea menor que z para las combinaciones válidas.When you experiment, use values where x is less than y, and y is less than z for valid combinations. Agregue el código siguiente a un nuevo método.Add the following code in a new method. Pruebe diferentes combinaciones:Try different combinations:

int[] numbers = Enumerable.Range(0, 100).ToArray();
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]}");

Compatibilidad con tipos para los índices y los rangosType support for indices and ranges

Los índices y los intervalos proporcionan una sintaxis clara y concisa para acceder a un único elemento o a un rango de elementos de una secuencia.Indexes and ranges provide clear, concise syntax to access a single element or a range of elements in a sequence. Normalmente, una expresión de índice devuelve el tipo de los elementos de una secuencia.An index expression typically returns the type of the elements of a sequence. Una expresión de rango suele devolver el mismo tipo de secuencia que la secuencia de origen.A range expression typically returns the same sequence type as the source sequence.

Cualquier tipo que proporcione un indexador con un parámetro Index o Range admite de manera explícita índices o rangos, respectivamente.Any type that provides an indexer with an Index or Range parameter explicitly supports indices or ranges respectively. Un indexador que toma un único parámetro Range puede devolver un tipo de secuencia diferente, como System.Span<T>.An indexer that takes a single Range parameter may return a different sequence type, such as System.Span<T>.

Importante

El rendimiento del código que usa el operador de rango depende del tipo del operando de la secuencia.The performance of code using the range operator depends on the type of the sequence operand.

La complejidad temporal del operador de rango depende del tipo de secuencia.The time complexity of the range operator depends on the sequence type. Por ejemplo, si la secuencia es un valor string o una matriz, el resultado es una copia de la sección especificada de la entrada, por lo que la complejidad temporal es O(N) (donde N es la longitud del rango).For example, if the sequence is a string or an array, then the result is a copy of the specified section of the input, so the time complexity is O(N) (where N is the length of the range). Por otro lado, si se trata de System.Span<T> o System.Memory<T>, el resultado hace referencia a la misma memoria auxiliar, lo que significa que no hay ninguna copia y que la operación es O(1) .On the other hand, if it's a System.Span<T> or a System.Memory<T>, the result references the same backing store, which means there is no copy and the operation is O(1).

Además de la complejidad temporal, esto provoca asignaciones y copias adicionales, lo que afecta al rendimiento.In addition to the time complexity, this causes extra allocations and copies, impacting performance. En el código sensible al rendimiento, considere la posibilidad de usar Span<T> o Memory<T> como el tipo de secuencia, ya que el operador de rango no realiza la asignación.In performance sensitive code, consider using Span<T> or Memory<T> as the sequence type, since the range operator does not allocate for them.

Un tipo es contable si tiene una propiedad denominada Length o Count con un captador accesible y un tipo de valor devuelto de int.A type is countable if it has a property named Length or Count with an accessible getter and a return type of int. Un tipo contable que no admite índices ni rangos de manera explícita podría admitirlos implícitamente.A countable type that doesn't explicitly support indices or ranges may provide an implicit support for them. Para más información, consulte las secciones Compatibilidad implícita de índices y Compatibilidad implícita de rangos de la nota de propuesta de características.For more information, see the Implicit Index support and Implicit Range support sections of the feature proposal note. Los rangos que usan la compatibilidad implícita del rango devuelven el mismo tipo de secuencia que la secuencia de origen.Ranges using implicit range support return the same sequence type as the source sequence.

Por ejemplo, los tipos de .NET siguientes admiten tanto índices como rangos: String, Span<T> y ReadOnlySpan<T>.For example, the following .NET types support both indices and ranges: String, Span<T>, and ReadOnlySpan<T>. List<T> admite índices, pero no rangos.The List<T> supports indices but doesn't support ranges.

Array tiene un comportamiento con más matices.Array has more nuanced behavior. Así, las matrices de una sola dimensión admiten índices y rangos,Single dimension arrays support both indices and ranges. mientras que las matrices multidimensionales no.Multi-dimensional arrays don't. El indexador de una matriz multidimensional tiene varios parámetros, no un parámetro único.The indexer for a multi-dimensional array has multiple parameters, not a single parameter. Las matrices escalonadas, también denominadas matriz de matrices, admiten tanto intervalos como indexadores.Jagged arrays, also referred to as an array of arrays, support both ranges and indexers. En el siguiente ejemplo se muestra cómo iterar por una subsección rectangular de una matriz escalonada.The following example shows how to iterate a rectangular subsection of a jagged array. Se itera por la sección del centro, excluyendo la primera y las últimas tres filas, así como la primera y las dos últimas columnas de cada fila seleccionada:It iterates the section in the center, excluding the first and last three rows, and the first and last two columns from each selected row:

var jagged = new int[10][]
{
   new int[10] { 0,  1, 2, 3, 4, 5, 6, 7, 8, 9},
   new int[10] { 10,11,12,13,14,15,16,17,18,19},
   new int[10] { 20,21,22,23,24,25,26,27,28,29},
   new int[10] { 30,31,32,33,34,35,36,37,38,39},
   new int[10] { 40,41,42,43,44,45,46,47,48,49},
   new int[10] { 50,51,52,53,54,55,56,57,58,59},
   new int[10] { 60,61,62,63,64,65,66,67,68,69},
   new int[10] { 70,71,72,73,74,75,76,77,78,79},
   new int[10] { 80,81,82,83,84,85,86,87,88,89},
   new int[10] { 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();
}

En todos los casos, el operador de rango para Array asigna una matriz para almacenar los elementos devueltos.In all cases, the range operator for Array allocates an array to store the elements returned.

Escenarios para los índices y los rangosScenarios for indices and ranges

A menudo usará rangos e índices cuando quiera analizar una parte de una secuencia más grande.You'll often use ranges and indices when you want to analyze a portion of a larger sequence. La nueva sintaxis es más clara al leer exactamente qué parte de la secuencia está implicada.The new syntax is clearer in reading exactly what portion of the sequence is involved. La función local MovingAverage toma un Range como su argumento.The local function MovingAverage takes a Range as its argument. El método enumera solo ese rango al calcular el mínimo, el máximo y la media.The method then enumerates just that range when calculating the min, max, and average. Pruebe con el código siguiente en su proyecto:Try the following code in your project:

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)).ToArray();