1. Evolución del dolor
Seguro que te ha pasado: no solo necesitas recorrer una colección, sino también saber el número de cada elemento. Por ejemplo, para mostrar una lista numerada, cambiar elementos con índices distintos de cero o hacer algo con cada tercero. En un for clásico — fácil:
// Todo normal, el clásico de C#:
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine($"{i}: {list[i]}");
}
Pero si empezaste a escribir en LINQ, de repente... ¡perdiste el índice! Todos esos bonitos .Where, .Select, .OrderBy de repente no te dan el número del elemento — solo el elemento en sí. Claro que te gustaría hacer algo así:
list.SelectWithIndex((item, index) => ...);
Pero nunca hubo un método estándar tipo SelectWithIndex. Aunque con la sobrecarga de Select puedes pillar el índice, pero solo... cuando quieres usar el índice sin transformar o proyectar, tenías que montar un lío con .Select extra, y eso hacía el código LINQ menos bonito y menos claro.
Cómo sobrevivíamos antes de .NET 9
En los viejos tiempos, los programadores de C# tenían sus propios hacks:
var result = list.Select((item, index) => new { item, index });
Y también — combinaciones con otros métodos LINQ, pero siempre era un poco "pegote", y no era lo que querías para que quedara bonito.
2. Nuevo método LINQ: Index — ¿qué es y para qué sirve?
Descripción oficial
En .NET 9 el equipo de desarrollo escuchó los lamentos de millones de programadores (bueno, decenas de miles en Twitter, que tampoco está mal) — y añadió a LINQ un nuevo método de extensión — Index. En la página oficial de la documentación de .NET 9 puedes encontrar esto:
Index() añade a cada elemento de la secuencia su número de orden, empezando por cero, y devuelve pares (valor, índice), sin tener que crear objetos anónimos ni escribir .Select((item, idx) => new { item, idx }).
Esto mola — ahora Index simplemente te devuelve para cada elemento un par: el propio elemento y su índice.
Signatura del método
public static IEnumerable<(T Element, int Index)> Index<T>(this IEnumerable<T> source);
Traducción a lenguaje humano: Para cada elemento de la colección pillas un "tuple" especial — Element y su Index. Ya está. No necesitas montar tipos anónimos.
3. Ejemplos de uso del método Index
Un ejemplo muy sencillo
Vamos a pillar una lista de frutas favoritas.
var fruits = new List<string> { "Manzana", "Plátano", "Naranja", "Kiwi" };
foreach (var (fruit, idx) in fruits.Index())
{
Console.WriteLine($"{idx}: {fruit}");
}
Salida:
0: Manzana
1: Plátano
2: Naranja
3: Kiwi
¡Así de fácil! Ahora puedes pillar el par "índice-valor" y usarlos dentro de cualquier consulta LINQ.
Integración con otros métodos LINQ
Index — es un miembro de pleno derecho de la familia LINQ. Puedes meterlo tranquilamente en las "cadenas".
Ejemplo 1: Filtrar elementos impares por índice
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));
Salida:
11, 13, 15, 17, 19
Ejemplo 2: Modificar elementos con índices pares
var users = new List<string> { "Ana", "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);
Salida:
ANA
igor
KATYA
denis
Ejemplo 3: Unir con otras colecciones por índice (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}");
Salida:
101: Alice
102: Bob
103: Charlie
4. Index en una app real
Vamos a añadir a nuestra app "eterna" de aprendizaje una nueva función: mostrar la lista de todos los estudiantes de nuestra colección con su número de orden (por ejemplo, para que el usuario pueda elegir el estudiante por número).
Ejemplo: Mostramos la lista de estudiantes con números
Supón que ya tenemos la clase Student de ejemplos anteriores:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
Creamos una pequeña lista de estudiantes:
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 }
};
Ahora, usando Index, mostramos todo bonito con números:
foreach (var (student, idx) in students.Index())
{
Console.WriteLine($"{idx + 1}. {student.Name} — Nota: {student.Grade}");
}
Aquí
idx + 1 — para que la numeración empiece en uno, no en cero.
Resultado:
1. Dasha — Nota: 5
2. Petya — Nota: 3
3. Vova — Nota: 4
4. Olya — Nota: 5
Utilidad práctica: Ahora, si el usuario quiere elegir un estudiante por número — ¡ya está! El código es más simple, sin contadores "a mano", máxima legibilidad — mínimos bugs.
5. Comparación: ¿por qué Index mola más que los viejos métodos?
Antes de .NET 9: el estilo antiguo
Antes, para pillar el elemento y su índice tenías que usar la sobrecarga .Select((item, index) => ...) y construir tipos anónimos:
var withIndexes = students.Select((student, index) => new { student, index });
Para pillar los campos que querías, tenías que escribir siempre .student, .index — y además el tipo es anónimo, nada de tuple bonito con nombre.
Con .NET 9: estilo siglo XXI
Ahora — no tienes que preocuparte por campos ni tipos anónimos. Todo claro:
foreach (var (student, idx) in students.Index())
{
// funciona out of the box, intuitivo, simple, bonito
}
El código es más limpio. Menos código, menos errores. Perfecto para cadenas LINQ largas, donde no quieres pensar en sobrecargas extra.
6. Detalles y peculiaridades de uso
Ámbito de aplicación
Index funciona con cualquier objeto que implemente la interfaz IEnumerable<T>. O sea — con todas las colecciones normales, arrays, resultados de otras consultas LINQ.
¿Qué tipo devuelve Index?
Devuelve una enumeración de tuples, donde el primer elemento es el objeto en sí (normalmente llamado Element), el segundo — Index (tipo int). Gracias a la sintaxis moderna de tuples en C#, puedes escribir directamente en el bucle foreach (var (element, index) in ...) y pillar ambos valores en variables separadas.
¿Se puede usar con Query Syntax?
No, Index es un método de extensión, la query syntax (LINQ tipo SQL) no lo soporta directamente. O sea, así no va:
// ¡No funciona!
var query = from s in students.Index() select ...;
Si quieres combinarlo, simplemente mete el método que quieras entre paréntesis y trabaja con el resultado como con una colección normal:
var query = from pair in students.Index()
where pair.Index > 1
select pair.Element;
Indexación: siempre desde cero
Index siempre empieza a contar desde cero, como casi todo en programación C#. Si necesitas empezar desde uno — solo suma 1 donde lo necesites.
8. Errores y peculiaridades — en qué fijarse
Muchos estudiantes al principio se lían entre Index y la sobrecarga .Select((item, index) => ...). Los errores más típicos:
— Intentan usar Index en query syntax: "¿Por qué no va?" — y es que solo funciona como método de extensión.
— Esperan que el índice empiece en 1, pero claro, empieza en 0.
— Piensan que Index modifica la colección original — pero, como todos los métodos LINQ, devuelve una nueva secuencia, sin tocar la original (enfoque inmutable).
Otra cosa interesante: si la colección es "perezosa" (recuerda: las consultas LINQ por defecto son perezosas), el método Index también calculará los índices según accedas a los elementos, no antes. Esto va genial para trabajar con secuencias grandes o incluso infinitas — la indexación siempre será correcta y no te va a petar la RAM.
GO TO FULL VERSION