Compartilhar via


Índices e intervalos

Intervalos e índices fornecem uma sintaxe sucinta para acessar elementos ou intervalos únicos em uma sequência.

Neste tutorial, você aprenderá como:

  • Utilize a sintaxe de intervalos em uma sequência.
  • Definir implicitamente um Range.
  • Entenda as decisões de design para o início e o fim de cada sequência.
  • Aprenda cenários para os tipos Index e Range.

Suporte a idiomas para índices e intervalos

Índices e intervalos fornecem uma sintaxe sucinta para acessar elementos ou intervalos únicos em uma sequência.

Esse suporte de linguagem depende de dois novos tipos e dois novos operadores:

Vamos começar com as regras para índices. Considere uma matriz sequence. O 0 índice é o mesmo que sequence[0]. O ^0 índice é o mesmo que sequence[sequence.Length]. A expressão sequence[^0] gera uma exceção, assim como sequence[sequence.Length] faz. Para qualquer número n, o índice ^n é o mesmo que 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

Você pode recuperar a última palavra com o ^1 índice. Adicione o seguinte código abaixo da inicialização:

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

Um intervalo especifica o início e o fim de um intervalo. O início do intervalo é inclusivo, mas o final do intervalo é exclusivo, o que significa que o início está incluído no intervalo, mas o final não está incluído no intervalo. O intervalo [0..^0] representa todo o intervalo, assim como [0..sequence.Length] representa todo o intervalo.

O código a seguir cria um subintervalo com as palavras "segundo", "terceiro" e "quarto". Ele inclui words[1] até words[3]. O elemento words[4] não está no intervalo.

string[] secondThirdFourth = words[1..4]; // contains "second", "third" and "fourth"

// < second >< third >< fourth >
foreach (var word in secondThirdFourth)
    Console.Write($"< {word} >"); 
Console.WriteLine();

O código a seguir retorna o intervalo com "nono" e "décimo". Ele inclui words[^2] e words[^1]. O índice words[^0] final não está incluído.

 string[] lastTwo = words[^2..^0]; // contains "ninth" and "tenth"

 // < ninth >< tenth >
 foreach (var word in lastTwo)
     Console.Write($"< {word} >"); 
 Console.WriteLine();

Os exemplos a seguir criam intervalos abertos para o início, o término ou ambos:

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

Você também pode declarar intervalos ou índices como variáveis. Em seguida, a variável pode ser usada dentro dos [ caracteres e ] :

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

O exemplo a seguir mostra muitos dos motivos para essas escolhas. Modifique x, y e z para tentar combinações diferentes. Quando você experimentar, use valores em que x seja menor que y, e y seja menor do que z para garantir combinações válidas. Adicione o código a seguir em um novo método. Experimente combinações diferentes:

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]}");

Não apenas matrizes dão suporte a índices e intervalos. Você também pode usar índices e intervalos com cadeia de caracteres ou Span<T>ReadOnlySpan<T>.

Conversões de expressões de operadores de intervalo implícitos

Ao usar a sintaxe de expressão do operador de intervalo, o compilador converte implicitamente os valores de início e de término em um Index e a partir deles, cria uma nova Range instância. O código a seguir mostra um exemplo de conversão implícita da sintaxe de expressão do operador de intervalo e sua alternativa explícita correspondente:

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'

Importante

Conversões implícitas de Int32 para Index lançam um ArgumentOutOfRangeException quando o valor é negativo. Da mesma forma, o Index construtor lança um ArgumentOutOfRangeException quando o value parâmetro é negativo.

Suporte a tipos para índices e intervalos

Índices e intervalos fornecem sintaxe clara e concisa para acessar um único elemento ou um intervalo de elementos em uma sequência. Uma expressão de índice normalmente retorna o tipo dos elementos de uma sequência. Uma expressão de intervalo normalmente retorna o mesmo tipo de sequência que a sequência de origem.

Qualquer tipo que forneça um indexador com um Index ou Range parâmetro dá suporte explicitamente a índices ou intervalos, respectivamente. Um indexador que usa um único Range parâmetro pode retornar um tipo de sequência diferente, como System.Span<T>.

Importante

O desempenho do código usando o operador de intervalo depende do tipo do operando de sequência.

A complexidade de tempo do operador de intervalo depende do tipo de sequência. Por exemplo, se a sequência for uma ou uma string matriz, o resultado será uma cópia da seção especificada da entrada, portanto, a complexidade do tempo será O(N) (em que N é o comprimento do intervalo). Por outro lado, se for um System.Span<T> ou um System.Memory<T>, o resultado fará referência ao mesmo repositório de backup, o que significa que não há cópia e a operação é O(1).

Além da complexidade de tempo, isso causa alocações e cópias extras, afetando o desempenho. No código que seja sensível ao desempenho, considere utilizar Span<T> ou Memory<T> como tipos de sequência, uma vez que o operador de intervalo não realiza alocação para eles.

Um tipo é contável se tiver uma propriedade nomeada Length ou Count com um getter acessível e um tipo de retorno de int. Um tipo contável que não dá suporte explicitamente a índices ou intervalos pode fornecer suporte implícito para eles. Para obter mais informações, consulte as seções de suporte para Índice Implícito e suporte para Intervalo Implícito da nota sobre proposta de funcionalidades. Os intervalos que usam o suporte ao intervalo implícito retornam o mesmo tipo de sequência que a sequência de origem.

Por exemplo, os seguintes tipos .NET dão suporte a índices e intervalos: String, Span<T>e ReadOnlySpan<T>. O List<T> dá suporte a índices, mas não a intervalos.

Array tem um comportamento mais sutil. Matrizes de dimensão única dão suporte a índices e intervalos. Matrizes multidimensionais não dão suporte a indexadores ou intervalos. O indexador de uma matriz multidimensional tem vários parâmetros, não um único parâmetro. Matrizes irregulares, também conhecidas como uma matriz de matrizes, dão suporte a intervalos e indexadores. O exemplo a seguir mostra como iterar uma subseção retangular de uma matriz irregular. Itera a seção no centro, excluindo as três primeiras e últimas linhas e as duas primeiras e últimas colunas de cada linha selecionada:

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();
}

Em todos os casos, o operador de intervalo para Array aloca uma matriz para armazenar os elementos retornados.

Cenários para índices e intervalos

Geralmente, você usará intervalos e índices quando quiser analisar uma parte de uma sequência maior. A nova sintaxe é mais clara ao ler exatamente qual parte da sequência está envolvida. A função local MovingAverage aceita um Range como argumento. Em seguida, o método enumera apenas esse intervalo ao calcular o mínimo, o máximo e a média. Experimente o seguinte código em seu projeto:

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))];

Uma observação sobre índices de intervalo e matrizes

Ao tirar um intervalo de uma matriz, o resultado é uma matriz copiada da matriz inicial, em vez de referenciada. Modificar valores na matriz resultante não alterará valores na matriz inicial.

Por exemplo:

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

Consulte também