C# 试题 27:LINQ 延迟执行
0521 以下代码输出什么?
难度: 实战
var list = new List<int> { 1, 2, 3 };
var query = list.Where(x => x > 1);
list.Add(4);
Console.WriteLine(string.Join(",", query));
- A.
2,3,4,查询在枚举时才读取源,看到新增元素 - B.
2,3 - C.
1,2,3,4 - D.
3,4
查看答案与解析
正确答案A
解析: Where 只构建查询,不读取源;真正执行发生在 string.Join 枚举时,此时源里已有 4,因此结果为 2、3、4。延迟执行最常见的坑就是“以为查询在定义时已固定”。
关键点: 查询是惰性的,源的变化会反映到枚举结果。
0522 以下代码输出什么?
难度: 进阶
var n = 0;
var query = Enumerable.Range(1, 3).Select(x => { n++; return x; });
Console.WriteLine(query.Count());
Console.WriteLine(query.Count());
Console.WriteLine(n);
- A.
3/3/3 - B.
3/3/0 - C.
3/3/6,每次 Count() 都重新枚举并执行委托 - D.
3/0/3
查看答案与解析
正确答案C
解析: Count() 会强制枚举整个序列,而查询每次枚举都重新执行 Select 的委托。两次 Count() 各执行 3 次,n 累计为 6。带副作用的查询被多次消费是生产事故的常见来源。
关键点: 延迟查询的副作用会随每次枚举重复发生。
0523 以下代码输出什么?
难度: 实战
var n = 0;
var list = Enumerable.Range(1, 3).Select(x => { n++; return x; }).ToList();
Console.WriteLine(list.Count());
Console.WriteLine(list.Count());
Console.WriteLine(n);
- A.
3/3/6 - B.
3/3/3,ToList 物化一次,之后统计的是内存列表 - C.
3/0/3 - D.
3/3/0
查看答案与解析
正确答案B
解析: ToList() 立即枚举并物化,Select 委托只执行 3 次;之后对内存列表的两次 Count() 不再触碰原查询,n 保持 3。需要多次消费时先物化是标准做法。
关键点: 物化(ToList/ToArray)切断与延迟查询的绑定。
0524 以下代码输出什么?
难度: 进阶
var n = 0;
var query = Enumerable.Range(1, 100).Select(x => { n++; return x; });
query.Any(x => x == 3);
Console.WriteLine(n);
- A.
100 - B.
1 - C.
0 - D.
3,Any 找到第一个匹配即短路,只读取了前 3 个元素
查看答案与解析
正确答案D
解析: Any 在谓词首次返回 true 时立即停止枚举:依次读 1、2、3,x==3 命中,n 为 3。若用 Count(x => x == 3) > 0 则会遍历全部 100 个元素。
关键点: Any 短路,Count 全量,判断存在性不要用 Count。
0525 对静态类型为 IEnumerable<int>、实际是惰性序列的 source,判断“是否非空”用 source.Any() 还是 source.Count() > 0,哪项正确?
难度: 进阶
- A. 两者等价,复杂度相同
- B. Any() 在第一个元素处短路;Count() 会枚举整个序列(若对象不是 ICollection),对无限序列 Count() 永不返回
- C. Count() 更快,因为它不执行谓词
- D. Any() 一定抛异常
查看答案与解析
正确答案B
解析: Any() 只需推进一次迭代器即返回;Count() > 0 在源不是 ICollection 时会完整遍历,无限序列上直接挂死。语义上 Any 表达“存在元素”,Count 表达“元素个数”,别用后者表达前者。
关键点: 判空用 Any(),尤其源可能是无限或昂贵时。
0526 以下代码运行结果是什么?
难度: 实战
Console.WriteLine(new int[0].First());
- A. 输出
0 - B. 输出
null - C. 运行抛
InvalidOperationException(序列不含元素) - D. 编译失败
查看答案与解析
正确答案C
解析: First() 对空序列抛 InvalidOperationException,不返回默认值。只有 FirstOrDefault 才在空序列时返回 default,两者契约不同,混用是经典失误。
关键点: First 与 FirstOrDefault 只差一个 OrDefault,行为差一个异常。
0527 以下代码运行结果是什么?
难度: 实战
var query = new[] { 1, 2, 3 }.Where(x => x > 0);
Console.WriteLine(query.Single());
- A. 运行抛
InvalidOperationException(序列包含多个元素) - B. 输出
1 - C. 输出
3 - D. 输出
1,Single 取第一个
查看答案与解析
正确答案A
解析: Single() 的契约是“恰好一个”,多于一个或为零都抛 InvalidOperationException。Single 不“取第一个”,取第一个是 First 的职责。
关键点: Single 是断言而非选择,失败即抛异常。
0528 以下代码运行结果是什么?
难度: 实战
Console.WriteLine(new int[0].SingleOrDefault());
Console.WriteLine(new[] { 1, 2 }.SingleOrDefault());
- A.
0/1 - B.
0/2 - C.
1/2 - D. 第一行输出
0;第二行抛InvalidOperationException,SingleOrDefault 只在“恰好一个”时成功
查看答案与解析
正确答案D
解析: SingleOrDefault 只在空序列时返回默认值;序列包含多个元素时仍抛 InvalidOperationException。它与 FirstOrDefault 的容错范围不同,不能想当然地“有多个就取第一个”。
关键点: OrDefault 只豁免“空”,不豁免“多”。
0529 以下代码输出什么?
难度: 进阶
var query = new[] { 1, 2, 3, 4 }.Where(x => { Console.Write(x); return x % 2 == 0; });
Console.Write(query.First());
- A.
24 - B.
122,Where 依次读取 1、2,命中后 First 立即返回 2 - C.
12342 - D.
12
查看答案与解析
正确答案B
解析: First() 推进管道直到拿到第一个匹配:Where 读取 1(打印 1,不匹配)、读取 2(打印 2,匹配)并产出,First 返回 2(打印 2)。输出为 122,后面 3、4 从未被读取。
关键点: 管道元素按需流动,短路的运算符决定读取边界。
0530 以下代码输出什么?
难度: 进阶
var funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++) funcs.Add(() => i);
Console.WriteLine(string.Join(",", funcs.Select(f => f())));
- A.
0,1,2 - B.
2,2,2 - C.
3,3,3,for 的循环变量是单一共享变量,闭包捕获同一变量 - D. 编译失败
查看答案与解析
正确答案C
解析: for 的循环变量在整个循环期间是同一个变量,三个 lambda 捕获的都是它,调用时它已是 3,因此输出三个 3。这与 foreach(C# 5 起每迭代独立变量)形成对比。
关键点: for 闭包共享变量,foreach 闭包每迭代独立。
0531 以下代码输出什么?
难度: 实战
var funcs = new List<Func<int>>();
foreach (var i in Enumerable.Range(0, 3)) funcs.Add(() => i);
Console.WriteLine(string.Join(",", funcs.Select(f => f())));
- A.
0,1,2,C# 5 起 foreach 每次迭代使用独立变量 - B.
3,3,3 - C.
2,2,2 - D. 编译失败
查看答案与解析
正确答案A
解析: 自 C# 5 起,foreach 的迭代变量每轮迭代都是新变量,闭包捕获各自的值,输出 0、1、2。这正是 0530 与 0531 的对照:循环形式决定闭包行为。
关键点: foreach 已修复共享变量陷阱,for 没有。
0532 以下代码输出什么?
难度: 实战
var funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
int j = i;
funcs.Add(() => j);
}
Console.WriteLine(string.Join(",", funcs.Select(f => f())));
- A.
3,3,3 - B.
2,2,2 - C. 编译失败
- D.
0,1,2,局部副本 j 每迭代独立
查看答案与解析
正确答案D
解析: 在循环体内声明局部变量 j 并复制 i,每个闭包捕获各自的 j,互不影响,输出 0、1、2。这是 for 循环闭包的经典修正写法。
关键点: 需要按值捕获时,复制到循环体内局部变量。
0533 以下代码输出什么?
难度: 进阶
var list = new List<int> { 3, 1, 2 };
var query = list.OrderBy(x => x);
list.Add(0);
Console.WriteLine(string.Join(",", query));
- A.
3,1,2,0 - B.
0,1,2,3,OrderBy 延迟执行,枚举时才读取并排序当前源 - C.
3,1,2 - D.
0,1,2,排序在调用 OrderBy 时已完成
查看答案与解析
正确答案B
解析: OrderBy 是延迟运算符:调用时不排序,枚举时才缓冲全部元素并排序。此时源已包含新增的 0,输出 0,1,2,3。注意排序虽延迟,但枚举时必须完整读取源(非流式)。
关键点: 延迟不等于立即执行,也别假设它是流式。
0534 以下代码输出什么?
难度: 实战
var list = new List<int> { 1, 2 };
var query = list.Select(x => x * 10);
Console.WriteLine(string.Join(",", query));
list.Add(3);
Console.WriteLine(string.Join(",", query));
- A.
10,20/10,20 - B.
10,20,30/10,20,30 - C.
10,20/10,20,30,每次枚举重新执行查询 - D.
10,20/ 抛异常
查看答案与解析
正确答案C
解析: 两次 string.Join 各触发一次完整枚举,第二次枚举时源已多出 3,得到 10,20,30。同一查询对象被重复枚举可能产生不同结果,这是延迟执行的自然推论。
关键点: 查询对象本身无状态,结果取决于枚举那一刻的源。
0535 关于 LINQ 运算符的立即/延迟执行,哪项正确?
难度: 进阶
- A.
Where调用时立即枚举源并缓存结果 - B.
OrderBy调用时立即完成排序 - C.
GroupBy调用后源已被完整读取 - D.
Count()调用时枚举整个源;Where/Select/OrderBy返回的查询在枚举前不执行
查看答案与解析
正确答案D
解析: LINQ 运算符分两类:Where/Select/OrderBy/GroupBy 等是延迟执行(返回查询),ToList/ToArray/Count/First 等会立即驱动枚举。GroupBy 虽延迟,但枚举时会一次性缓冲整个源。
关键点: “调用即执行”与“调用只组装查询”要先分清。
0536 source 静态类型为 IEnumerable<int>,实际对象是 List<int>。调用 source.Count() 的时间复杂度是?
难度: 进阶
- A. O(1),Count() 检测到对象实现 ICollection<int> 后直接读 Count 属性
- B. O(n),Count() 总是全量枚举
- C. O(n log n)
- D. 抛 InvalidOperationException
查看答案与解析
正确答案A
解析: Enumerable.Count() 内部先做 ICollection<T>/ICollection 探测,命中就直接返回 Count 属性,不枚举。因此“Count() 一定 O(n)”是误解;但对自定义惰性序列(非 ICollection)仍是全量枚举。
关键点: Count() 对 ICollection 有优化,对纯 IEnumerable 才全量。
0537 以下代码输出什么?
难度: 实战
Console.WriteLine(Enumerable.Empty<int>().Any());
Console.WriteLine(Enumerable.Empty<int>().All(x => x > 0));
- A.
True/False - B.
False/False - C.
False/True,All 对空序列恒真(空真) - D.
True/True
查看答案与解析
正确答案C
解析: Any() 要求至少一个元素,空序列为 false;All() 要求所有元素满足谓词,空序列没有反例,按逻辑学“空真(vacuous truth)”返回 true。用 All 判断“每个都满足”时要注意空序列的隐式通过。
关键点: 空序列上 All 为 true 常被忽视。
0538 以下代码输出什么?
难度: 进阶
var query = new[] { 1, 2, 3, 4, 5 }.Select(x => { Console.Write(x); return x; });
query.Take(2).ToList();
- A.
12345,Take 也会遍历全部 - B.
12,Take(2) 取到两个元素后停止,不再读取源 - C.
1234 - D. 无输出
查看答案与解析
正确答案B
解析: Take(2) 在拿到两个元素后停止推进源迭代器,Select 的委托只执行两次,输出 12。这既是延迟执行的体现,也是“Take 提前终止”可以避免昂贵源计算的原因。
关键点: Take 短路上游管道,未消费部分不执行。
0539 以下代码输出什么?
难度: 实战
int threshold = 2;
var query = new[] { 1, 2, 3, 4 }.Where(x => x > threshold);
threshold = 3;
Console.WriteLine(string.Join(",", query));
- A.
3,4 - B.
2,3,4 - C.
1,2,3,4 - D.
4,谓词在枚举时求值,读取最新 threshold
查看答案与解析
正确答案D
解析: Where 的谓词闭包捕获的是 threshold 变量本身而非其值,枚举时 threshold 已是 3,只有 4 满足 x > 3。闭包捕获的“变量”在延迟执行中读的是最新值。
关键点: 谓词闭包看的是枚举时的变量状态。
0540 以下代码输出什么?
难度: 实战
var query = new[] { "a", "b" }.Where(s => s.Length > 5);
var result = query.FirstOrDefault();
Console.WriteLine(result is null ? "null" : result);
- A.
null,FirstOrDefault 无匹配时返回 null(引用类型默认值) - B.
"" - C. 抛
InvalidOperationException - D.
"a"
查看答案与解析
正确答案A
解析: 谓词无匹配时序列为空,FirstOrDefault 返回 default(string) 即 null。对引用类型,OrDefault 的“默认值”就是 null,使用前判空是必须的。
关键点: FirstOrDefault 的默认值是类型默认值,引用类型即 null。