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.
GO TO FULL VERSION