Indici e intervalli

Gli intervalli e gli indici forniscono una sintassi concisa per accedere a singoli elementi o intervalli in una sequenza.

Questa esercitazione illustra come:

  • Usare la sintassi per gli intervalli in una sequenza.
  • Definire in modo implicito un oggetto Range.
  • Comprendere le decisioni a livello di progettazione per l'inizio e la fine di ogni sequenza.
  • Analizzare gli scenari per i tipi Index e Range.

Supporto del linguaggio per indici e intervalli

Gli indici e gli intervalli forniscono una sintassi concisa per accedere a singoli elementi o intervalli in una sequenza.

Questo supporto per il linguaggio si basa su due nuovi tipi e due nuovi operatori:

  • System.Index rappresenta un indice in una sequenza.
  • Indice dell'operatore ^end , che specifica che un indice è relativo alla fine di una sequenza.
  • System.Range rappresenta un intervallo secondario di una sequenza.
  • Operatore ..range , che specifica l'inizio e la fine di un intervallo come operandi.

Per iniziare, ecco come funzionano le regole per gli indici. Prendere in considerazione una matrice sequence. L'indice 0 è uguale a sequence[0]. L'indice ^0 è uguale a sequence[sequence.Length]. L'espressione sequence[^0] genera un'eccezione, esattamente come sequence[sequence.Length] avviee. Per qualsiasi numero n, l'indice ^n è uguale a 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

È possibile recuperare l'ultima parola con l'indice ^1. Aggiungere il codice seguente sotto l'inizializzazione:

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

Un intervallo specifica inizio e fine di un intervallo. L'inizio dell'intervallo è inclusivo, ma la fine dell'intervallo è esclusiva, ovvero l'inizio è incluso nell'intervallo, ma la fine non è inclusa nell'intervallo. L'intervallo [0..^0] rappresenta l'intero intervallo, proprio come [0..sequence.Length] rappresenta l'intero intervallo.

Il codice seguente crea un intervallo secondario con le parole "quick", "brown" e "fox". Include da words[1] a words[3]. L'elemento words[4] non è compreso nell'intervallo.

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

Il codice seguente restituisce l'intervallo con "lazy" e "dog". Include words[^2] e words[^1]. L'indice finale words[^0] non viene incluso. Aggiungere anche il codice seguente:

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

Gli esempi seguenti creano intervalli aperti alle estremità, per l'inizio, la fine o entrambe:

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

È anche possibile dichiarare gli intervalli o gli indici come variabili. In seguito la variabile può essere usata all'interno dei caratteri [ 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();

L'esempio seguente illustra molti dei motivi per cui sono state fatte tali scelte. Modificare x, y, e z per provare combinazioni diverse. Durante le varie prove, usare valori per cui x è minore di y e y è minore di z per le combinazioni valide. Aggiungere il codice seguente in un nuovo metodo. Provare combinazioni diverse:

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 solo le matrici supportano indici e intervalli. È anche possibile usare indici e intervalli con string, Span<T>o ReadOnlySpan<T>.

Conversioni di espressioni dell'operatore di intervallo implicite

Quando si usa la sintassi dell'espressione dell'operatore range, il compilatore converte in modo implicito i valori iniziale e finale in un oggetto Index e da essi, crea una nuova Range istanza. Il codice seguente illustra una conversione implicita di esempio dalla sintassi dell'espressione dell'operatore range e la corrispondente alternativa esplicita:

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

Le conversioni implicite da Int32 a generano Index un'eccezione ArgumentOutOfRangeException quando il valore è negativo. Analogamente, il Index costruttore genera un'eccezione ArgumentOutOfRangeException quando il value parametro è negativo.

Supporto dei tipi per indici e intervalli

Gli indici e gli intervalli forniscono una sintassi chiara e concisa per accedere a un singolo elemento o a un intervallo di elementi in una sequenza. Un'espressione di indice restituisce in genere il tipo degli elementi di una sequenza. Un'espressione di intervallo restituisce in genere lo stesso tipo di sequenza della sequenza di origine.

Qualsiasi tipo che fornisce un indicizzatore con un Index parametro o Range supporta in modo esplicito indici o intervalli rispettivamente. Un indicizzatore che accetta un singolo Range parametro può restituire un tipo di sequenza diverso, ad esempio System.Span<T>.

Importante

Le prestazioni del codice che usano l'operatore range dipendono dal tipo dell'operando di sequenza.

La complessità temporale dell'operatore di intervallo dipende dal tipo di sequenza. Ad esempio, se la sequenza è una string matrice o , il risultato è una copia della sezione specificata dell'input, quindi la complessità temporale è O(N) (dove N è la lunghezza dell'intervallo). D'altra parte, se è un System.Span<T> oggetto o System.Memory<T>, il risultato fa riferimento allo stesso archivio di backup, il che significa che non è presente alcuna copia e l'operazione è O(1).

Oltre alla complessità del tempo, ciò causa allocazioni e copie aggiuntive, che influisce sulle prestazioni. Nel codice sensibile alle prestazioni prendere in considerazione l'uso Span<T> o Memory<T> come tipo di sequenza, poiché l'operatore di intervallo non viene allocato per tali tipi.

Un tipo è conteggiabile se ha una proprietà denominata Length o Count con un getter accessibile e un tipo restituito di int. Un tipo conteggiabile che non supporta in modo esplicito indici o intervalli può fornire un supporto implicito per tali indici. Per altre informazioni, vedere le sezioni Supporto indice implicito e Supporto intervallo implicito della nota sulla proposta di funzionalità. Gli intervalli che usano il supporto dell'intervallo implicito restituiscono lo stesso tipo di sequenza della sequenza di origine.

Ad esempio, i tipi .NET seguenti supportano sia gli indici che gli intervalli: String, Span<T>e ReadOnlySpan<T>. List<T> supporta gli indici, ma non supporta gli intervalli.

Array ha un comportamento più sfumato. Le matrici a dimensione singola supportano sia indici che intervalli. Le matrici multidimensionali non supportano indicizzatori o intervalli. L'indicizzatore per una matrice multidimensionale ha più parametri, non un singolo parametro. Matrici irregolari, dette anche matrice di matrici, supportano sia intervalli che indicizzatori. Nell'esempio seguente viene illustrato come eseguire l'iterazione di una sottosezione rettangolare di una matrice frastagliata. Esegue l'iterazione della sezione al centro, escludendo la prima e le ultime tre righe e le prime e le ultime due colonne di ogni riga selezionata:

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

In tutti i casi, l'operatore range per Array alloca una matrice per archiviare gli elementi restituiti.

Scenari per indici e intervalli

Spesso si usano intervalli e indici quando si vuole analizzare una parte di una sequenza più grande. La nuova sintassi è più chiara nella lettura esatta della parte della sequenza. La funzione locale MovingAverage accetta un Range come argomento. In seguito il metodo enumera solo quell'intervallo durante il calcolo dei valori minimo e massimo e della media. Provare a includere il codice seguente nel progetto:

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

Nota sugli indici e le matrici di intervalli

Quando si accetta un intervallo da una matrice, il risultato è una matrice copiata dalla matrice iniziale, anziché fare riferimento. La modifica dei valori nella matrice risultante non modificherà i valori nella matrice iniziale.

Ad esempio:

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

Vedi anche