返回题库高级 .NET 刷题C# 选择题 · 第 29 / 50 篇

C# 试题 29:迭代器与异步流

0561 以下代码输出什么?

难度: 实战

static IEnumerable<int> M()
{
    Console.Write("X");
    yield return 1;
}

var query = M();
Console.Write("Y");
Console.WriteLine(query.First());
  • A. XY1
  • B. YX1,调用 M() 只构建状态机,枚举时才执行方法体
  • C. Y1
  • D. YX
查看答案与解析

正确答案B

解析:yield 的方法被编译成状态机,调用 M() 只返回枚举器、不执行任何方法体;First() 首次 MoveNext 才打印 X 并产出 1。输出为 YX1

关键点: 迭代器方法体的执行被推迟到首次枚举。

0562 以下代码输出什么?

难度: 进阶

static IEnumerable<int> M()
{
    Console.Write("X");
    yield break;
}

var query = M();
Console.Write("Y");
Console.WriteLine(query.Count());
  • A. YX0,yield break 前的语句也在首次 MoveNext 时才执行
  • B. XY0
  • C. Y0
  • D. YX1
查看答案与解析

正确答案A

解析: 只要方法里存在 yield,整个方法体(包括 yield break 之前的语句)都延迟到首次 MoveNext 执行。因此 Y 先打印,随后枚举打印 X 并得到 Count=0,输出 YX0

关键点: 延迟的是“整个方法体”,不只是 yield 之后的代码。

0563 以下代码输出什么?

难度: 实战

static IEnumerable<int> M()
{
    int i = 0;
    yield return i++;
    yield return i++;
}

var a = M().GetEnumerator();
var b = M().GetEnumerator();
a.MoveNext(); a.MoveNext();
b.MoveNext();

Console.WriteLine($"{a.Current} {b.Current}");
  • A. 1 1
  • B. 0 0
  • C. 1 0,每次 GetEnumerator 创建独立状态机
  • D. 0 1
查看答案与解析

正确答案C

解析: 每次调用 M()/GetEnumerator 都生成独立的状态机实例,局部变量 i 互不相干:a 已推进两次(Current=1),b 只推进一次(Current=0)。

关键点: 迭代器状态属于单个枚举器,枚举器之间不共享。

0564 以下代码输出什么?

难度: 实战

static IEnumerable<int> M()
{
    try { yield return 1; yield return 2; }
    finally { Console.Write("F"); }
}

foreach (var x in M())
{
    Console.Write(x);
    break;
}
  • A. 1
  • B. 12F
  • C. 1F2
  • D. 1F,break 会 Dispose 枚举器,finally 同步执行
查看答案与解析

正确答案D

解析: foreach 在提前 break 时会对枚举器调用 Dispose,迭代器状态的 finally 块随之执行。第一轮输出 1,break 触发 finally 输出 F,结果为 1F

关键点: 迭代器的清理不依赖 GC,提前退出也会走 finally。

0565 以下代码输出什么?

难度: 进阶

static IEnumerable<int> M()
{
    try { yield return 1; }
    finally { Console.Write("F"); }
}

Console.WriteLine(string.Join(",", M()));
  • A. 1F
  • B. F1,MoveNext 返回 false(触发 finally)先于 Join 拼接结果
  • C. 1
  • D. 编译失败,yield 不能出现在 try/finally 中
查看答案与解析

正确答案B

解析: string.Join 完整枚举后,最后一次 MoveNext 返回 false 时会执行 finally(打印 F),之后 Join 才完成拼接并输出 1。因此控制台先出现 F 再出现 1。

关键点: finally 在枚举自然结束时执行,且发生在 Join 产出结果之前。

0566 以下代码输出什么?

难度: 进阶

static IEnumerable<int> M()
{
    try { yield return 1; }
    finally { Console.Write("F"); }
}

var e = M().GetEnumerator();
e.MoveNext();
Console.Write("X");
  • A. X,未 Dispose 枚举器,finally 不执行
  • B. XF
  • C. FX
  • D. F
查看答案与解析

正确答案A

解析: 手动 GetEnumerator 不会自动释放:没有 using/foreach,枚举器从未被 Dispose,finally 不会执行,只输出 X。未释放的迭代器会一直占着 try/finally 内的资源直到 GC 兜底。

关键点: 手动枚举必须显式 Dispose(using 或 foreach),清理不是自动的。

0567 以下代码输出什么?

难度: 实战

class Resource : IDisposable
{
    public void Dispose() => Console.Write("D");
}

static IEnumerable<int> M()
{
    using var r = new Resource();
    yield return 1;
}

foreach (var x in M()) break;
  • A. 无输出,using 资源在 GC 时才释放
  • B. 输出 1D
  • C. 输出 D,foreach 提前 break 触发枚举器 Dispose,using 立即释放
  • D. 输出 1
查看答案与解析

正确答案C

解析: 迭代器方法里的 using 会被编译进状态机的 Dispose 路径:foreach 取到第一个元素后 break,枚举器被 Dispose,r.Dispose() 同步执行输出 D。资源释放不依赖 GC。

关键点: 迭代器内 using 的生命周期 = 枚举器生命周期,提前退出也会释放。

0568 以下代码结果是什么?

难度: 实战

static IEnumerable<int> M()
{
    try { yield return 1; }
    catch (Exception) { }
}
  • A. 正常运行
  • B. 运行抛 InvalidOperationException
  • C. 运行抛 CompilerException
  • D. 编译失败(CS1626):yield 不能出现在带 catch 的 try 块中
查看答案与解析

正确答案D

解析: 编译器限制:yield return 不能出现在带 catch 子句的 try 块中(CS1626)。因为异常跨越状态机恢复点时无法可靠映射 catch 语义。try/finally 中允许 yield,try/catch 中不允许。

关键点: yield 与 try/catch 互斥,与 try/finally 兼容。

0569 以下代码结果是什么?

难度: 进阶

Func<IEnumerable<int>> f = () => { yield return 1; };
  • A. 正常运行
  • B. 编译失败(CS1621):yield 不能出现在 lambda 或匿名方法中
  • C. 运行抛 NotSupportedException
  • D. 返回空序列
查看答案与解析

正确答案B

解析: yield 只能出现在普通(非匿名)方法中,lambda 与匿名方法内使用会报 CS1621。需要迭代器逻辑时只能抽成具名方法。

关键点: 别试图在 lambda 里 yield,编译器不允许。

0570 两个线程各自用 foreach 枚举同一个迭代器方法返回的实例,哪项正确?

难度: 进阶

  • A. 每次 foreach 都创建独立枚举器与独立状态机,互不干扰;只有并发调用同一个枚举器的 MoveNext 才不安全
  • B. 同一 IEnumerable 不允许并发枚举,会抛 InvalidOperationException
  • C. 两个线程共享同一状态,输出会交错
  • D. IEnumerable 是线程安全的,同一枚举器可并发 MoveNext
查看答案与解析

正确答案A

解析: 迭代器返回的 IEnumerable 每次 GetEnumerator 都新建状态机,两个 foreach 互不影响。真正不安全的做法是并发调用同一个枚举器的 MoveNext——那会破坏共享状态,属于未定义行为。

关键点: “IEnumerable 可并发枚举”与“IEnumerator 可并发推进”是两回事。

0571 以下代码输出什么?

难度: 实战

static async IAsyncEnumerable<int> M()
{
    Console.Write("S");
    await Task.Yield();
    yield return 1;
}

var query = M();
Console.Write("E");
await foreach (var x in query) Console.Write(x);
  • A. ES
  • B. SE1
  • C. ES1,async 迭代器同样延迟,调用时不执行方法体
  • D. E1S
查看答案与解析

正确答案C

解析: async 迭代器与同步迭代器一样延迟:调用 M() 只创建异步枚举器,await foreach 首次 MoveNextAsync 才执行方法体(打印 S、await、产出 1),输出 ES1

关键点: 异步流同样惰性,调用不等于开始执行。

0572 以下代码输出什么?

难度: 进阶

static async IAsyncEnumerable<int> M()
{
    try { yield return 1; yield return 2; }
    finally { Console.Write("F"); }
}

await foreach (var x in M())
{
    Console.Write(x);
    break;
}
  • A. 12F
  • B. 1
  • C. 1F2
  • D. 1F,break 触发异步 Dispose,finally 执行
查看答案与解析

正确答案D

解析: await foreach 提前 break 时会对异步枚举器调用 DisposeAsync,async 迭代器中的 finally 随之执行。第一轮输出 1,break 后输出 F,结果为 1F

关键点: 异步流的清理同样在提前退出时发生,通过 DisposeAsync 触发。

0573 关于 await foreach (var x in seq.ConfigureAwait(false))await foreach (var x in seq) 的区别,哪项正确?

难度: 进阶

  • A. ConfigureAwait(false) 让迭代器完全同步执行
  • B. 前者使迭代器内部的 await 不再恢复到捕获的 SynchronizationContext 上,避免上下文切换;无同步上下文时两者无差别
  • C. 两者行为完全相同,ConfigureAwait 对异步流无效
  • D. ConfigureAwait(false) 会取消迭代
查看答案与解析

正确答案B

解析: 对异步流调用 ConfigureAwait(false) 影响的是迭代器体内每个 await 的恢复方式:默认恢复回捕获的 SynchronizationContext(如 UI 线程),false 则在线程池继续,避免无谓的上下文切换。控制台等无上下文场景二者等价。

关键点: ConfigureAwait 作用在迭代器的内部 await 上,不是外层循环。

0574 以下代码运行结果是什么?

难度: 进阶

using System.Runtime.CompilerServices;

static async IAsyncEnumerable<int> M([EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 0; i < 5; i++)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Delay(1, ct);
        yield return i;
    }
}

using var cts = new CancellationTokenSource();
cts.Cancel();
await foreach (var x in M().WithCancellation(cts.Token)) { }
Console.Write("done");
  • A. 抛 OperationCanceledException,token 通过 WithCancellation 传入 [EnumeratorCancellation] 参数
  • B. 正常输出 done
  • C. 编译失败,WithCancellation 只能用于同步迭代器
  • D. 抛 TaskCanceledException,与 OperationCanceledException 不同
查看答案与解析

正确答案A

解析: WithCancellation(cts.Token) 把 token 注入标有 [EnumeratorCancellation] 的参数,首次 MoveNextAsync 就执行 ThrowIfCancellationRequestedOperationCanceledException,“done”不会打印。(TaskCanceledException 是 OCE 的子类,这里抛出的是 OCE。)

关键点: 异步流取消的标准通道是 WithCancellation + [EnumeratorCancellation]。

0575 编写“异步生成序列”的方法,正确声明是?

难度: 实战

  • A. async Task<IEnumerable<int>> M(),用 yield 返回序列
  • B. Task<IAsyncEnumerable<int>> M(),内部 await + yield
  • C. async IAsyncEnumerable<int> M(),可用 await 与 yield,配合 await foreach 消费
  • D. IAsyncEnumerable<int> M() 内可直接 await,无需 async
查看答案与解析

正确答案C

解析: 异步流必须声明为 async IAsyncEnumerable<T>,才能同时使用 awaityieldasync Task<IEnumerable<T>> 里不能用 yield;Task<IAsyncEnumerable<T>> 包裹异步流是反模式。消费端用 await foreach

关键点: 异步流的“async”与“迭代器”必须在同一个返回类型上。

0576 以下代码输出什么?

难度: 进阶

int n = 0;
IEnumerable<int> Gen()
{
    while (n < 3) yield return n++;
}

var query = Gen();
Console.WriteLine(query.Count());
Console.WriteLine(query.Count());
  • A. 3 / 3
  • B. 3 / 抛异常
  • C. 0 / 0
  • D. 3 / 0,每次枚举重新执行方法体,但捕获的外部变量跨枚举共享
查看答案与解析

正确答案D

解析: 迭代器方法体在每次枚举时重新执行,但闭包捕获的外部变量 n 是共享的:第一次 Count() 把 n 推到 3,第二次枚举时 n < 3 已不成立,结果为 0。这正说明“重新执行”不等于“状态重置”。

关键点: 捕获外部状态的迭代器,多次枚举之间状态会延续。

0577 以下代码输出什么?

难度: 进阶

static IEnumerable<int> M()
{
    int i = 0;
    while (i < 3) yield return i++;
}

var query = M();
Console.WriteLine(query.Sum());
Console.WriteLine(query.Sum());
  • A. 3 / 0
  • B. 3 / 3,局部变量属于每次枚举新建的状态机,两次枚举互不影响
  • C. 6 / 0
  • D. 0 / 0
查看答案与解析

正确答案B

解析: 与 0576 对照:i 是迭代器方法内的局部变量,位于每次枚举新建的状态机中,两次 Sum() 各自从 0 开始,都得到 0+1+2=3。

关键点: 局部状态随枚举器新建而重置,外部捕获状态则共享。

0578 以下代码输出什么?

难度: 实战

static async IAsyncEnumerable<int> M()
{
    try { yield return 1; }
    finally { Console.Write("F"); }
}

await foreach (var x in M()) { }
Console.Write("E");
  • A. FE,完整消费结束时 finally 执行
  • B. EF
  • C. E
  • D. F
查看答案与解析

正确答案A

解析: 循环体为空但会完整消费:第一次 MoveNextAsync 产出 1,第二次返回 false 时 finally 执行(打印 F),循环结束后打印 E,输出 FE。异步流的 finally 在枚举自然结束时同样生效。

关键点: 完整消费结束也会触发 finally,异步同步皆如此。

0579 以下代码输出什么?

难度: 进阶

static IEnumerable<int> M(int x)
{
    Console.Write(x);
    yield return x;
}

int n = 1;
var query = M(n);
n = 2;

Console.WriteLine(query.First());
  • A. 21
  • B. 22
  • C. 11,参数在调用时求值(1),方法体在枚举时执行(打印 1)并产出 1
  • D. 12
查看答案与解析

正确答案C

解析: 实参在调用 M(n) 时求值,状态机记住的是 1;方法体延迟到 First() 枚举时执行,打印 1 并产出 1。n=2 的修改不影响已捕获的参数。输出 11

关键点: 参数按调用时快照,方法体按枚举时执行。

0580 两个任务并发 await foreach 同一个 IAsyncEnumerable 实例,哪项正确?

难度: 进阶

  • A. 会抛异常,异步流不支持并发
  • B. 两个消费者共享同一状态机,元素均分
  • C. 同一枚举器可被并发 MoveNextAsync
  • D. 每次 await foreach 创建独立枚举器;方法体若只使用局部状态则互不干扰,共享外部状态才需同步
查看答案与解析

正确答案D

解析: 与同步迭代器一致,每次 await foreach 通过 GetAsyncEnumerator 得到独立状态机,并发消费同一异步流是安全的——前提是迭代器方法体不依赖共享外部可变状态。同一个枚举器并发 MoveNextAsync 仍不安全。

关键点: 并发“消费”安全,并发“推进同一枚举器”不安全。

官方资料

当前分类

C# 选择题

查看全部分类 →
  1. 27C# 试题 27:LINQ 延迟执行20 题
  2. 28C# 试题 28:LINQ 分组与连接20 题
  3. 29C# 试题 29:迭代器与异步流20 题
  4. 30C# 试题 30:日期时间、Guid 与 Random20 题
  5. 31C# 试题 31:文件与流20 题
ESC

输入关键词开始搜索