首页 / C# 入门教程 / 筛选与投影

C# 入门教程

筛选与投影

本教程共 100 篇 · 第 69 篇 · 更新于 2026-07-31 · 约 10 分钟阅读

C#C# 入门教程编程语言LINQWhereSelect投影

69. 筛选与投影

本节目标:学完你能用 Where 按条件挑数据,用 Select 把它映射成想要的任意形状,并知道匿名类型、元组、record 在投影时该怎么选。

筛选(filtering)和投影(projection)是 LINQ 里最高频的两个操作。一个负责「挑哪些」,一个负责「变成啥」。本章把它们讲透,并补齐适用场景、对比和坑。

Where:按条件筛选

Where 接收一个「谓词(predicate)」——即返回 bool 的 lambda。为 true 的元素留下,为 false 的丢弃。

List<Student> students =
[
    new Student(1, "小明", 90),
    new Student(2, "小红", 85),
    new Student(3, "小刚", 90),
];

var top = students.Where(s => s.Score >= 90);

foreach (var s in top)
{
    Console.WriteLine(s.Name);
}

class Student
{
    public int Id { get; }
    public string Name { get; }
    public double Score { get; }
    public Student(int id, string name, double score)
    {
        Id = id; Name = name; Score = score;
    }
}

输出:

小明
小刚

s => s.Score >= 90 就是谓词。s 是每个学生的代表,返回「是否优秀」。

Tip

谓词里可以写任意复杂的条件。多条件用 &&|| 连接:s => s.Score >= 60 && s.Name.Length > 1。也可以用 ! 取反,比如「不及格的」写成 s => s.Score < 60

用到元素下标:Where 的索引重载

Where 还有一个重载,lambda 的第二个参数是元素在序列里的下标(index)。比如「只要偶数位置的学生」:

var alternate = students.Where((s, i) => i % 2 == 0);

foreach (var s in alternate)
{
    Console.WriteLine(s.Name);
}

输出:

小明
小刚

i 从 0 开始,i % 2 == 0 挑出下标 0、2……也就是第 1、3 个。这个重载在「按位置取样」「隔一个取一个」时很方便,无需自己维护计数器。

组合多个条件

筛选条件可以用方法串起来,也可以写在同一个 lambda 里。下面只挑「90 分且名字含『小』」:

var result = students
    .Where(s => s.Score >= 90)
    .Where(s => s.Name.Contains("小"));

foreach (var s in result)
{
    Console.WriteLine(s.Name);
}

输出:小明 小刚(两人都含「小」)。拆成两个 Where 更易读,也可合并成一个 &&

Note

串多个 Where 和写在一个 lambda 里用 && 结果相同。多个 Where 会多做几层枚举,但 LINQ 是延迟执行、流式处理,性能差异通常可忽略。可读性优先:条件多且各自独立时,拆开更清楚。

Select:映射到新数据

Select 把每个元素「变成」另一个东西。最简单是提取某个字段:

var names = students.Select(s => s.Name);

foreach (var name in names)
{
    Console.WriteLine(name);
}

输出:

小明
小红
小刚

这里每个 Student 被「投影」成了 string(名字)。元素数量不变,类型变了。

投影成计算后的值

Select 后面可以是任意表达式。比如把分数换算成百分制等级:

var levels = students.Select(s => s.Score >= 90 ? "优秀" : "良好");

foreach (var lv in levels)
{
    Console.WriteLine(lv);
}

输出:优秀 良好 优秀。每个元素经过一次计算,映射到新值。

匿名类型投影

最常用的是「取几个字段、临时组合」。C# 的匿名类型(anonymous type)用 new { ... } 现场造一个只有属性的小类型,连名字都不用起:

var cards = students.Select(s => new
{
    s.Name,
    Grade = s.Score >= 90 ? "优秀" : "良好"
});

foreach (var c in cards)
{
    Console.WriteLine($"{c.Name}{c.Grade}");
}

输出:

小明:优秀
小红:良好
小刚:优秀

c.Namec.Grade 是编译器自动生成的属性。匿名类型适合「只在局部用一下」的场景,省去专门定义类。

用元组做轻量投影

除了匿名类型,C# 还可用**元组(tuple / ValueTuple)**做投影,写法是圆括号加字段名:

var pairs = students.Select(s => (s.Name, s.Score));

foreach (var p in pairs)
{
    Console.WriteLine($"{p.Name} 考了 {p.Score} 分");
}

输出:

小明 考了 90 分
小红 考了 85 分
小刚 考了 90 分

匿名类型和元组都适合「临时取几个字段」,区别在:元组是值类型、字段名写在括号里,跨方法返回时类型写法更轻;匿名类型属性只读、由编译器生成。想「有名字、可比较、可跨方法传」的长久结构,还是用正式 recordclass(第 49 章)。

Note

三者怎么选:局部临时用→匿名类型或元组皆可;需要值相等比较、语义清晰→用 record要挂方法或长期复用→用 class。别为了一次性投影硬去定义一个完整类,那是过度设计。

筛选与投影连用

真实代码里两者常一起出现:先挑,再变。

var report = students
    .Where(s => s.Score >= 85)
    .Select(s => new { s.Name, s.Score });

foreach (var r in report)
{
    Console.WriteLine($"{r.Name} 考了 {r.Score} 分");
}

输出:

小明 考了 90 分
小红 考了 85 分
小刚 考了 90 分
Tip

顺序上通常先 WhereSelect:先缩小数据量,再做映射,效率更好。虽然 LINQ 是延迟执行,但少算一点总没错。

适用场景:何时用 Where / Select

  • Where:从一堆数据里「按规则留子集」——查及格的、查名字带某字的、查价格区间内的商品。凡是「过滤」都找它。
  • Select:需要「换一种形态输出」——只取名字列表、把实体转成给前端的 DTO、把分数算成等级。凡是「变形」都找它。
  • 两者连用:最常见于「先过滤、再整形」的报表/列表场景。
Warning

Select 是「一进一出」的一对一映射。如果你要的是「一个元素展开成多个」(比如每个订单展开成多件商品),Select 做不到——它仍是单序列。那种一对多要用 SelectMany(进阶章会讲)。别在 Select 里返回集合又期望被自动摊平,那样你会得到「集合的集合」。

与其它操作对比

操作输入→输出数量用途
Where同量(只减不增)按条件过滤
Select同量(一对一变形)投影/映射
SelectMany一对多展开扁平化嵌套序列
TakeWhile从头取到条件破条件性截取(与 Where 不同:TakeWhile 一破就停)

注意 WhereTakeWhile 的差别:Where 扫描全部、留下所有满足条件的;TakeWhile 从头开始,一旦某元素不满足条件就立刻停止,后面不再看。按需求选对工具。

新手容易踩的坑

第一,Where 的 lambda 必须返回 bool。写成 s => s.Score(返回数字)会编译报错,因为那不是谓词。

第二,匿名类型只在当前方法内有用。想返回给别的方法,得换成具名类型,否则类型推断会失效。

第三,误以为 Select 会改原集合。Select 只产出新序列,原始 students 一点没变。LINQ 默认不修改数据源。

第四,Select 会「拉平」成单序列。若要一对多展开(如每个订单展开成多个商品),得用 SelectMany,那是进阶话题。

第五,忘记 Where/Select 也是延迟执行。只写不遍历,什么都不会发生;同一查询反复遍历会重复计算,必要时 ToList() 缓存。

常见疑问解答

问:Where 和 if 在循环里筛选有什么区别? if 要你手写循环、手动把命中的元素塞进新集合;Where 一句话描述规则、返回可枚举序列,少写样板且不易错。Where 底层其实也是遍历,但意图更清楚。

问:Select 之后元素的顺序会变吗? 不会。Select 保持原顺序,只做「逐个变形」。想重排顺序请用 OrderBy(下一章)。

问:匿名类型和元组哪个更好? 没有绝对好坏。局部、只读、字段少,用哪个都行;匿名类型从 C# 早期就有、生态成熟;元组写法更短、是值类型(少一次堆分配)。跨方法返回且想明确类型名时,优先 record

小结

Where 用谓词筛选元素,Select 把元素映射成新形状,匿名类型/元组让「取部分字段」零成本。两者配合,几乎能表达任何「取数 + 变形」的需求。记住先 WhereSelect 更高效,也别把 Select 当成能展开集合的万能钥匙。下一章看排序、分组与去重。