Partilhar via


Índices e intervalos

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

Neste tutorial, irá aprender a:

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

Suporte linguístico para índices e intervalos

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

Esse suporte a idiomas depende de dois novos tipos e dois novos operadores:

  • System.Index representa um índice em uma sequência.
  • O índice do operador ^final , que especifica que um índice é relativo ao final de uma sequência.
  • System.Range representa um subintervalo de uma sequência.
  • O operador ..range , que especifica o início e o fim de um intervalo como seus operandos.

Comecemos pelas regras dos í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] lança uma exceção, tal como sequence[sequence.Length] o faz. Para qualquer número n, o índice ^n é o mesmo que sequence.Length - n.

string[] words = [
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumps",    // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
];              // 9 (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]}");

Um intervalo especifica o início e o fim de um intervalo. O início do intervalo é inclusivo, mas o fim do intervalo é exclusivo, o que significa que o início está incluído no intervalo, mas o fim 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 "quick", "brown" e "fox". words[1] Inclui através de words[3]. O elemento words[4] não está no intervalo.

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

O código a seguir retorna o intervalo com "preguiçoso" e "cão". words[^2] Inclui e words[^1]. O índice words[^0] final não está incluído. Adicione também o seguinte código:

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

Os exemplos a seguir criam intervalos que são abertos para o início, o fim ou ambos:

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

Você também pode declarar intervalos ou índices como variáveis. A variável pode então ser usada dentro dos [ caracteres e ] :

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

O exemplo a seguir mostra muitas das razões para essas escolhas. Modifique x, ye z tente combinações diferentes. Ao experimentar, use valores em x que é menor que , e y é menor que yz para combinações válidas. Adicione o seguinte código em um novo método. Experimente diferentes combinações:

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 suportam índices e intervalos. Você também pode usar índices e intervalos com string, Span<T>, ou ReadOnlySpan<T>.

Conversões implícitas de expressão do operador de intervalo

Ao usar a sintaxe da expressão do operador de intervalo, o compilador converte implicitamente os valores inicial e final 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 da 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çar um ArgumentOutOfRangeException quando o valor é negativo. Da mesma forma, o construtor lança um ArgumentOutOfRangeException quando o Indexvalue parâmetro é negativo.

Suporte de tipo 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 a um indexador um Index parâmetro ou suporte explicitamente índices ou Range 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 é uma ou uma matriz, então o resultado é uma string cópia da seção especificada da entrada, então a complexidade de tempo é O(N) (onde N é o comprimento do intervalo). Por outro lado, se for um ou um System.Span<T>System.Memory<T>, o resultado faz referência ao mesmo armazenamento de suporte, 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. Em código sensível ao desempenho, considere usar Span<T> ou Memory<T> como o tipo de sequência, uma vez que o operador de intervalo não aloca 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 suporta explicitamente índices ou intervalos pode fornecer um suporte implícito para eles. Para obter mais informações, consulte as seções Suporte de índice implícito e Suporte de intervalo implícito da nota de proposta derecurso. Os intervalos que usam suporte de intervalo implícito retornam o mesmo tipo de sequência que a sequência de origem.

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

Array tem um comportamento mais matizado. Matrizes de dimensão única suportam índices e intervalos. As matrizes multidimensionais não suportam indexadores ou intervalos. O indexador para uma matriz multidimensional tem vários parâmetros, não um único parâmetro. As matrizes irregulares, também conhecidas como matrizes de matrizes, suportam intervalos e indexadores. O exemplo a seguir mostra como iterar uma subseção retangular de uma matriz irregular. Ele itera a seção no centro, excluindo a primeira e a última três linhas, e a primeira e as duas ú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 aloca uma matriz para Array armazenar os elementos retornados.

Cenários para índices e intervalos

Você geralmente 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 MovingAverage local toma um Range como argumento. Em seguida, o método enumera apenas esse intervalo ao calcular o min, o max e a média. Tente 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 nota sobre índices e matrizes de intervalo

Ao tirar um intervalo de uma matriz, o resultado é uma matriz que é copiada da matriz inicial, em vez de referenciada. A modificação de valores na matriz resultante não alterará os 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