Condividi tramite


Indici e intervalli

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

In questa esercitazione si apprenderà come:

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

Supporto della lingua 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.

Iniziamo con le regole per gli indici. Si consideri 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, come sequence[sequence.Length] avviene. Per qualsiasi numero n, l'indice ^n è uguale a 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

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

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

Un intervallo specifica l'inizio e la 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, esattamente come [0..sequence.Length] rappresenta l'intero intervallo.

Il codice seguente crea un intervallo secondario con le parole "second", "third" e "fourth". Comprende words[1] fino a words[3]. L'elemento words[4] non è compreso nell'intervallo.

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

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

Il codice seguente restituisce l'intervallo con "nono" e "decimo". È incluso words[^2] e words[^1]. L'indice words[^0] finale non è incluso.

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

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

Gli esempi seguenti creano intervalli aperti per l'inizio, la fine o entrambi:

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

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

L'esempio seguente illustra molti dei motivi per cui queste scelte sono disponibili. Modificare x, ye z per provare combinazioni diverse. Quando si sperimenta, usare i valori in cui x è minore di ye 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 implicite di espressioni dell'operatore di intervallo

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 Index lanciano un'eccezione ArgumentOutOfRangeException quando il valore è negativo. Analogamente, il costruttore Index genera un'eccezione ArgumentOutOfRangeException quando il parametro value è negativo.

Supporto ai 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> o un 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à temporale, ciò causa allocazioni e copie aggiuntive, che influiscono sulle prestazioni. Nel codice sensibile alle prestazioni, prendere in considerazione l'uso di Span<T> o Memory<T> come tipo di sequenza, poiché l'operatore di intervallo non effettua allocazioni per questi 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ù complesso. 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. Il metodo enumera quindi solo l'intervallo durante il calcolo di min, max e average. Provare 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.

Per 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

Vedere anche