CodeGym /课程 /C# SELF /LINQ新方法: Index

LINQ新方法: Index

C# SELF
第 32 级, 课程 3
可用

1. 痛苦的进化史

你肯定遇到过这种情况:不光要遍历集合,还得知道每个元素的编号。比如要输出带编号的列表、只改非零索引的元素,或者每隔三个处理一次。在普通的 for 里,这事儿超简单:

// 都很熟悉,C# 经典写法:
for (int i = 0; i < list.Count; i++)
{
    Console.WriteLine($"{i}: {list[i]}");
}

但如果你开始用 LINQ 写代码,突然……索引就没了!那些漂亮的 .Where、.Select、.OrderBy,都不给你元素的编号——只给你元素本身。你肯定想这样写:

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

但其实标准库里根本没有 SelectWithIndex 这种方法。虽然 Select 的重载能拿到索引,但……如果你只想用索引而不想做投影或转换,就得多写一堆 .Select,本来很优雅的 LINQ 代码就变得乱七八糟。

在 .NET 9 之前大家怎么活的

以前 C# 程序员有自己的“黑科技”:

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

还有各种和别的 LINQ 方法组合的骚操作,但总觉得不够“原生”,也不够优雅。

2. LINQ 新方法 Index —— 是啥?有啥用?

官方说明

.NET 9 的开发团队终于听到了无数程序员(好吧,推特上成千上万的吐槽,也不少了)的呼声——给 LINQ 加了个新扩展方法 Index。在 .NET 9 官方文档 上是这么说的:

Index() 会给序列里的每个元素加上它的编号(从 0 开始),返回 (值, 索引) 这样的对,不用你手动 new 匿名对象,也不用写 .Select((item, idx) => new { item, idx }) 这种啰嗦代码。

这功能很强——Index 直接给你每个元素和它的索引一对。

方法签名

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

翻成人话: 集合里的每个元素你都能拿到一个特殊的“元组”——Element 和它的 Index。就这样。再也不用造匿名类型了。

3. Index 方法的用法示例

超简单例子

来,咱们搞个最爱的水果列表。

var fruits = new List<string> { "苹果", "香蕉", "橙子", "猕猴桃" };

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

输出:

0: 苹果
1: 香蕉
2: 橙子
3: 猕猴桃

就这么简单!你现在可以优雅地拿到“索引-值”对,在任何 LINQ 查询里用。

和其他 LINQ 方法一起玩

Index 是 LINQ 家族的正式成员!可以随便插到“链”里。

例1:按索引过滤奇数位置的元素

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

输出:

11, 13, 15, 17, 19

例2:只改偶数索引的元素

var users = new List<string> { "安娜", "伊戈尔", "卡佳", "丹尼斯" };
var modified = users.Index()
                    .Select(pair => pair.Index % 2 == 0 ? pair.Element.ToUpper() : pair.Element.ToLower());

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

输出:

安娜
伊戈尔
卡佳
丹尼斯

例3:按索引合并其他集合(像 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}");

输出:

101: Alice
102: Bob
103: Charlie

4. Index 在实际项目里的用法

来,给咱们“永恒”学生管理小项目加个新功能:把所有学生带编号输出(比如让用户可以按编号选学生)。

例子:输出带编号的学生列表

假设你已经有了 Student 类:

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

造个小学生列表:

var students = new List<Student>
{
    new Student { Name = "达莎", Grade = 5 },
    new Student { Name = "彼佳", Grade = 3 },
    new Student { Name = "沃瓦", Grade = 4 },
    new Student { Name = "奥莉娅", Grade = 5 }
};

现在用 Index 优雅地带编号输出:

foreach (var (student, idx) in students.Index())
{
    Console.WriteLine($"{idx + 1}. {student.Name} — 分数: {student.Grade}");
}
这里 idx + 1 —— 就是让编号从 1 开始,看着更习惯。

结果:

1. 达莎 — 分数: 5
2. 彼佳 — 分数: 3
3. 沃瓦 — 分数: 4
4. 奥莉娅 — 分数: 5

实用性:现在用户想按编号选学生——直接搞定!代码更简单,不用“手动”计数器,易读性拉满,bug 也少。

5. 对比:Index 比老办法爽在哪?

.NET 9 之前:老派写法

以前要拿到元素和索引,只能用 .Select((item, index) => ...) 的重载,还得造匿名类型:

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

想拿字段还得写 .student、.index,而且类型是匿名的,没有漂亮的命名元组。

.NET 9 起:21 世纪风格

现在——不用管字段、匿名类型,直接来:

foreach (var (student, idx) in students.Index())
{
    // 开箱即用,直观、简单、优雅
}

代码更干净。更少代码,更少 bug。LINQ 长链里用起来也不用再想重载的事儿。

6. 使用细节和注意点

适用范围

Index 能用在任何实现了 IEnumerable<T> 的对象上。也就是——所有常见集合、数组、LINQ 查询结果都行。

Index 返回什么类型?

它返回的是元组序列,第一个元素是对象本身(一般叫 Element),第二个是 Index(类型 int)。C# 现代元组语法让你可以直接在循环里写 foreach (var (element, index) in ...),两个值直接进变量。

能用 Query Syntax 吗?

不能,Index 是扩展方法,query syntax(SQL 风格 LINQ)不直接支持。比如这样不行:

// 不行!
var query = from s in students.Index() select ...;

想结合用,只要把方法包进括号,像普通集合一样用就行:

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

索引总是从 0 开始

Index 总是从 0 开始计数,就像 C# 里大多数东西一样。如果你想从 1 开始——自己加 1 就行。

8. 易错点和小坑——注意啥

很多同学一开始会把 Index 和 .Select((item, index) => ...) 的重载搞混。常见错误有:

—— 想在 query syntax 里用 Index:“为啥不行?”——它只能当扩展方法用。

—— 以为索引会从 1 开始,其实肯定是 0。

—— 以为 Index 会改原集合——其实和所有 LINQ 方法一样,它返回新序列,原集合不变(不可变风格)。

还有个有意思的点:如果集合是“惰性”的(记住:LINQ 查询默认都是惰性),Index 也是按需计算索引,不会提前算好。这对大集合甚至无限序列特别友好——索引永远是对的,也不会炸内存。

2
任务
C# SELF, 第 32 级, 课程 3
已锁定
列表中的行号
列表中的行号
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION