CodeGym /Corsi /C# SELF /Nuovo metodo LINQ: Index

Nuovo metodo LINQ: Index

C# SELF
Livello 32 , Lezione 3
Disponibile

1. Evoluzione del dolore

Scommetto che ti è già capitato: non basta scorrere una collezione, ma ti serve anche sapere il numero di ogni elemento. Tipo per stampare una lista numerata, cambiare elementi con indici diversi da zero o fare qualcosa con ogni terzo elemento. Nel classico for — easy:

// Tutto normale, la classica C#:
for (int i = 0; i < list.Count; i++)
{
    Console.WriteLine($"{i}: {list[i]}");
}

Ma se hai iniziato a scrivere in LINQ, all’improvviso… perdi l’indice! Tutti quei bellissimi .Where, .Select, .OrderBy non ti danno il numero dell’elemento — solo l’elemento stesso. Ovviamente, vorresti fare così:

list.SelectWithIndex((item, index) => ...);

Ma un metodo standard tipo SelectWithIndex non è mai esistito. Anche se con l’overload di Select puoi ottenere l’indice, però… se vuoi usare l’indice senza trasformare o senza proiettare, dovevi inventarti qualche workaround con .Select aggiuntivi, e questo rendeva il codice LINQ meno chiaro.

Come si sopravviveva prima di .NET 9

Ai vecchi tempi i programmatori C# avevano i loro trucchetti:

var result = list.Select((item, index) => new { item, index });

E anche combinazioni con altri metodi LINQ, ma non era mai davvero "integrato", e non era proprio quello che volevi per la bellezza del codice.

2. Nuovo metodo LINQ: Index — cos’è e perché usarlo?

Descrizione ufficiale

In .NET 9 il team di sviluppo ha ascoltato le lamentele di milioni di programmatori (ok, decine di migliaia su Twitter, che comunque non sono pochi) — e ha aggiunto a LINQ un nuovo metodo di estensione — Index. Sulla pagina ufficiale della documentazione .NET 9 trovi scritto:

Index() aggiunge a ogni elemento della sequenza il suo numero d’ordine, a partire da zero, e restituisce coppie (valore, indice), senza dover creare oggetti anonimi o scrivere .Select((item, idx) => new { item, idx }).

Potente — ora Index ti restituisce per ogni elemento una coppia: l’elemento stesso e il suo indice.

Firma del metodo

public static IEnumerable<(T Element, int Index)> Index<T>(this IEnumerable<T> source);

Tradotto in parole povere: Per ogni elemento della collezione ottieni una "tuple" speciale — Element e il suo Index. Tutto qui. Non devi più inventarti tipi anonimi.

3. Esempi di utilizzo del metodo Index

Esempio super semplice

Prendiamo una lista dei frutti preferiti.

var fruits = new List<string> { "Mela", "Banana", "Arancia", "Kiwi" };

foreach (var (fruit, idx) in fruits.Index())
{
    Console.WriteLine($"{idx}: {fruit}");
}

Output:

0: Mela
1: Banana
2: Arancia
3: Kiwi

Così semplice! Ora puoi ottenere elegantemente la coppia "indice-valore" e usarla in qualsiasi query LINQ.

Integrazione con gli altri metodi LINQ

Index — è un vero membro della famiglia LINQ! Puoi tranquillamente metterlo nelle "chain".

Esempio 1: Filtrare elementi con indici dispari

var numbers = Enumerable.Range(10, 10); // 10, 11, ... 19
var oddIndexes = numbers.Index()
                        .Where(pair => pair.Index % 2 == 1)
                        .Select(pair => pair.Element);

Console.WriteLine(string.Join(", ", oddIndexes));

Output:

11, 13, 15, 17, 19

Esempio 2: Modificare elementi con indici pari

var users = new List<string> { "Anna", "Igor", "Katya", "Denis" };
var modified = users.Index()
                    .Select(pair => pair.Index % 2 == 0 ? pair.Element.ToUpper() : pair.Element.ToLower());

foreach (var name in modified)
    Console.WriteLine(name);

Output:

ANNA
igor
KATYA
denis

Esempio 3: Unire con altre collezioni per indice (tipo zip)

var ids = new[] { 101, 102, 103 };
var names = new[] { "Alice", "Bob", "Charlie" };

var merged = names.Index()
                  .Join(ids.Index(), 
                        namePair => namePair.Index,
                        idPair => idPair.Index,
                        (namePair, idPair) => (idPair.Element, namePair.Element));

foreach (var (id, name) in merged)
    Console.WriteLine($"{id}: {name}");

Output:

101: Alice
102: Bob
103: Charlie

4. Index in una vera applicazione

Aggiungiamo alla nostra "eterna" app didattica una nuova funzione: stampiamo la lista di tutti gli studenti dalla nostra collezione con il loro numero d’ordine (così l’utente può scegliere lo studente per numero).

Esempio: Stampiamo la lista degli studenti con i numeri

Supponiamo di avere già la classe Student dagli esempi precedenti:

public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
}

Creiamo una piccola lista di studenti:

var students = new List<Student>
{
    new Student { Name = "Dasha", Grade = 5 },
    new Student { Name = "Petya", Grade = 3 },
    new Student { Name = "Vova", Grade = 4 },
    new Student { Name = "Olya", Grade = 5 }
};

Ora, usando Index, stampiamo tutto con i numeri:

foreach (var (student, idx) in students.Index())
{
    Console.WriteLine($"{idx + 1}. {student.Name} — Voto: {student.Grade}");
}
Qui idx + 1 — così la numerazione parte da uno, non da zero.

Risultato:

1. Dasha — Voto: 5
2. Petya — Voto: 3
3. Vova — Voto: 4
4. Olya — Voto: 5

Utilità pratica: Ora, se l’utente vuole scegliere uno studente per numero — è tutto pronto! Il codice è più semplice, niente più contatori "manuali", massima leggibilità — zero bug.

5. Confronto: perché Index è più figo dei vecchi metodi?

Prima di .NET 9: stile vecchia scuola

Prima, per ottenere insieme elemento e indice dovevi usare l’overload .Select((item, index) => ...) e costruire tipi anonimi:

var withIndexes = students.Select((student, index) => new { student, index });

Per ottenere i campi che ti servivano, dovevi sempre scrivere .student, .index — e il tipo era anonimo, niente tuple belle e nominate.

Con .NET 9: stile XXI secolo

Ora — non devi più preoccuparti di campi, tipi anonimi. Tutto trasparente:

foreach (var (student, idx) in students.Index())
{
    // funziona out of the box, intuitivo, semplice, bello
}

Il codice è più pulito. Meno codice, meno errori. Perfetto per le chain LINQ lunghe, dove non vuoi pensare a overload extra.

6. Sfumature e particolarità d’uso

Ambito di applicazione

Index funziona con qualsiasi oggetto che implementa l’interfaccia IEnumerable<T>. Quindi — con tutte le collezioni normali, array, risultati di altre query LINQ.

Che tipo restituisce Index?

Restituisce una sequenza di tuple, dove il primo elemento è l’oggetto stesso (di solito chiamato Element), il secondo è Index (tipo int). Grazie alla sintassi moderna delle tuple in C# puoi scrivere direttamente nel ciclo foreach (var (element, index) in ...) e ottenere entrambi i valori subito nelle variabili che vuoi.

Si può usare con Query Syntax?

No, Index è un metodo di estensione, la query syntax (LINQ stile SQL) non lo supporta direttamente. Quindi così non va:

// Non funziona!
var query = from s in students.Index() select ...;

Se vuoi combinarlo, basta mettere il metodo tra parentesi e lavorare col risultato come con una normale collezione:

var query = from pair in students.Index()
            where pair.Index > 1
            select pair.Element;

Indicizzazione: sempre da zero

Index parte sempre da zero, come la maggior parte delle cose in C#. Se vuoi partire da uno — aggiungi 1 dove ti serve.

8. Errori e particolarità — a cosa stare attenti

Molti studenti all’inizio si confondono tra Index e l’overload .Select((item, index) => ...). Gli errori più comuni sono:

— Provano a usare Index nella query syntax: "Perché non funziona?" — ma funziona solo come metodo di estensione.

— Si aspettano che l’indice parta da 1, ma ovviamente parte da 0.

— Pensano che Index modifichi la collezione originale — ma, come tutti i metodi LINQ, restituisce una nuova sequenza senza modificare l’originale (approccio immutable).

Un’altra cosa interessante: se la collezione è "lazy" (ricorda: le query LINQ di default sono lazy), anche il metodo Index calcola gli indici man mano che accedi agli elementi, non prima. Perfetto per lavorare con sequenze grandi o anche infinite — l’indicizzazione sarà sempre corretta e non ti farà esplodere la RAM.

2
Compito
C# SELF, livello 32, lezione 3
Bloccato
Numerazione delle righe in una lista
Numerazione delle righe in una lista
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION