我写了个用 yield return 返回过滤结果的 C# 方法、在方法开头加了参数为空就抛异常的校验,调用处也老老实实包了 try-catch,结果传 null 进去那个异常死活没被 catch 住一路飞到最外层,盯着代码看半天才反应过来整个方法体压根没在调用那一刻执行的深度复盘

我写了个 C# 方法,接收一个集合和阈值、用 yield return 逐个吐出大于阈值的元素,很注意防御地在方法第一行就校验:集合为 null 就抛 ArgumentNullException,调用方也规规矩矩把调用包在 try-catch 里准备优雅降级。可上线后一个 null 传进来程序直接崩了:异常确实抛了,却没被那个明明包着调用的 try-catch 接住,而是穿过它一路冒泡到最顶层。我以为是 catch 写错类型(不是,catch 的就是它,连 catch(Exception) 都没接住)、以为是别的线程抛的(也不是)。直到在方法第一行校验处和调用处分别打断点才看清:调用那一行执行完、方法体里那句校验根本没进去,直到后面 foreach 遍历返回值那一刻断点才跳进方法第一行、异常这才抛出来。根因是只要方法里有 yield return,C# 编译器就会把整个方法改写成一个状态机,调用它时方法体一行都不执行、只返回一个迭代器对象,方法体里的代码(包括放在最开头的参数校验)要等到有人真正遍历(MoveNext)这个迭代器时才逐段执行——这就是延迟执行/惰性求值,它把计算的定义和计算的触发在时间上彻底分开了。由此带来三颗雷:校验异常推迟到遍历时才抛、从而逃过包在调用处的 try-catch;每次重新遍历都从头重跑整个方法体、副作用和查询执行多遍;方法依赖的资源若遍历时已释放就出错。正解是把需要立即发生的事(参数校验)拆到一个不含 yield 的外层普通方法里让它随调用立即执行、把延迟产出留在内层私有迭代器方法里,并在需要结果稳定或多次使用时用 ToList/ToArray 主动物化一次。这篇复盘从故障现场讲到 yield 为何让方法体延迟执行、立即与延迟执行对比、怎么诊断,再到外层校验内层迭代器的完整正解与骨架,以及 LINQ 延迟执行/多次枚举重跑/闭包延迟取值/Task 未 await 等同类坑,和分清声明计算与触发计算、看清延迟把异常副作用推到了何时何地的认知。

我写了个用 yield return 返回过滤结果的 C# 方法、在方法开头加了参数为空就抛异常的校验,调用处也老老实实包了 try-catch,结果传了 null 进去那个异常死活没被 catch 住、一路飞到了最外层,我盯着代码看了半天才反应过来整个方法体压根没在调用那一刻执行

这是一次让我把 C# 里"yield return"这件事,从"一种写迭代器的语法糖",重新理解成"整个方法体都被推迟到遍历时才执行"的事故。我写了个用 yield return 返回过滤后数据的方法,谨慎地在方法开头加了"参数为 null 就抛异常"的校验,调用处也老老实实包了 try-catch。结果传 null 进去,那个异常死活没被 catch 住、一路飞到了最外层把程序干崩了。我盯着代码看了半天,才反应过来一件颠覆我直觉的事:yield return 的方法,在你调用它的那一刻,方法体里一行代码都不会执行——包括我放在最开头的那句参数校验。这篇就把这次"校验异常没在该抛的时候抛"的事故,从头到尾复盘一遍。

故障现场:参数校验的异常,没被调用处的 try-catch 接住

我的方法长这样:接收一个集合和一个阈值,过滤出大于阈值的元素,用 yield return 逐个吐出来。我很注意防御,在方法第一行就校验:如果传进来的集合是 null,立刻抛 ArgumentNullException。调用方也很规矩,把调用包在 try-catch 里,准备捕获这个异常、优雅降级。

可上线后,一个 null 传进来,程序直接崩了。我看着异常堆栈一脸懵:ArgumentNullException 确实抛出来了,可它没被那个明明就包着调用的 try-catch 接住,而是穿过它、一路冒泡到了最顶层。我第一反应是"是不是 catch 写错了类型",检查发现 catch 的就是 ArgumentNullException(连 catch(Exception) 兜底都没接住);又怀疑"是不是异常在别的线程抛的",也不是。直到我在方法第一行的校验处和调用处分别打了断点,才看清真相——调用那一行执行完,方法体里那句校验根本没进去;直到后面 foreach 遍历返回值的那一刻,断点才跳进方法第一行,异常这才抛出来。

// 我的方法: 第一行就校验参数, 我以为调用时就会校验
IEnumerable<int> FilterAbove(List<int> source, int threshold)
{
    if (source == null)                       // ← 我以为调用时就执行这句
        throw new ArgumentNullException(nameof(source));

    foreach (var x in source)
        if (x > threshold)
            yield return x;                   // ← 有 yield, 整个方法体变成"延迟执行"
}

// 调用处: 老老实实包了 try-catch
try
{
    var result = FilterAbove(null, 10);       // ① 我以为这里就抛异常了
    // ...其实这一行【什么都没执行】, 只是拿到了一个"待执行的迭代器"
    foreach (var n in result)                 // ② 真正执行方法体、抛异常的是这里!
        Console.WriteLine(n);
}
catch (ArgumentNullException ex)
{
    // 我以为能接住 ① 的异常
    Console.WriteLine("接住了: " + ex.Message);
}

// 实际: 若 foreach 不在这个 try 范围内(比如 result 被 return 出去到别处遍历),
//       异常就在【别处遍历时】才抛, 这个 try-catch 根本不在现场, 接不住。

问题被钉死在这个认知错位上:我以为"调用 FilterAbove(null, 10)"就等于"执行了方法体、跑了那句校验",但只要方法里有 yield return,编译器就会把整个方法改写成一个状态机,调用它时什么都不执行,只是返回一个迭代器对象;方法体里的代码——包括最开头的参数校验——要等到有人真正去遍历这个迭代器时,才一段一段地执行。我那句精心放在第一行的校验,它的异常根本不在"调用时"抛,而在"遍历时"抛;而我的 try-catch 只包住了调用、没包住真正遍历的地方(或者遍历发生在被 return 出去之后的别处),自然接不住。我把"声明了这个计算"当成了"执行了这个计算",可 yield 恰恰把这两者彻底分了家。

第一件事:想明白 yield return 为什么让整个方法体"延迟执行"

把这次事故彻底想清楚,关键是理解yield return 的方法,返回的不是"算好的结果",而是一个"知道怎么一步步算"的迭代器;方法体里的代码是这台状态机的"剧本",只在每次被 MoveNext()(也就是 foreach 推进)时,才执行到下一个 yield return 为止。

C# 编译器看到方法里有 yield return,会做一件很神奇的事:把这个方法整体改写成一个实现了 IEnumerator 的状态机类。你调用这个方法时,它只是 new 出这个状态机对象并返回,方法体一行都不跑。之后每次遍历推进一步(MoveNext()),状态机才从上次 yield 的地方继续往下执行,直到撞上下一个 yield return 吐出一个值、然后再次暂停。这就是"延迟执行 / 惰性求值":计算的定义(方法体)和计算的触发(遍历)被分开了,中间隔着一个你看不见的"暂停"。我的参数校验,作为方法体的第一句,自然也被卷进了这个延迟里——它不在调用时执行,而在第一次 MoveNext() 时执行。

// 概念上, 编译器把上面的 FilterAbove 改写成了类似这样的状态机:
// (你调用 FilterAbove 时, 只是 new 了它, 方法体一行没跑)
IEnumerable<int> FilterAbove(List<int> source, int threshold)
    => new FilterAboveStateMachine(source, threshold);   // 仅创建, 不执行

// 验证"延迟"最直观的实验:
IEnumerable<int> Demo()
{
    Console.WriteLine(">>> 方法体开始执行了");   // 这句什么时候打印?
    yield return 1;
    yield return 2;
}

var seq = Demo();                 // 这里【不会】打印 ">>> 方法体开始执行了"
Console.WriteLine("已调用 Demo, 但还没遍历");
foreach (var x in seq)            // 直到这里第一次 MoveNext, 才打印那句
    Console.WriteLine(x);

// 输出顺序证明了一切:
//   已调用 Demo, 但还没遍历
//   >>> 方法体开始执行了      ← 延迟到遍历时才执行!
//   1
//   2

想通这一层,我才明白自己错在哪:我默认"调用一个方法 = 执行它的方法体",这对普通方法成立,但对 yield 迭代器方法不成立——调用它只是拿到一份"计算的配方",真正下厨是在遍历的时候。这不仅让我的参数校验异常推迟到了遍历时(从而逃过了包在调用处的 try-catch),还埋着另外两颗雷:一是如果调用方遍历两次这个返回值,整个方法体会从头执行两遍(里面的副作用、数据库查询全跑两遍);二是如果方法依赖的资源(比如一个 using 的连接)在遍历时已经被释放,遍历就会炸。yield 的便利背后,是"声明"与"执行"的彻底分离,而我一直把它们当成同一件事。

第二件事:正解——参数校验立即执行(拆成两段),并避免重复遍历

找到根因,正解就清晰了:要让参数校验在"调用时"就执行,必须把它从迭代器方法里"拎出来"——做法是把一个方法拆成两个:外层是普通方法(不含 yield)、立即校验参数,校验通过后再调用内层私有的迭代器方法(含 yield、延迟执行)。同时,对延迟序列要警惕重复遍历,需要结果稳定/多次用时,及早用 ToList() 物化一次。

// 正解1: 拆成"立即执行的外层 + 延迟执行的内层迭代器"
// 外层: 普通方法, 没有 yield → 方法体在【调用时】就执行 → 校验立即生效
public IEnumerable<int> FilterAbove(List<int> source, int threshold)
{
    if (source == null)                          // 调用时就抛, try-catch 接得住
        throw new ArgumentNullException(nameof(source));
    return FilterAboveIterator(source, threshold);  // 再交给延迟的内层
}

// 内层: 私有迭代器, 含 yield → 延迟执行, 但参数已被外层保证非 null
private IEnumerable<int> FilterAboveIterator(List<int> source, int threshold)
{
    foreach (var x in source)
        if (x > threshold)
            yield return x;
}

// 现在: 调用 FilterAbove(null, 10) 当场抛 ArgumentNullException, 被 try-catch 接住 ✓

// 正解2: 避免重复遍历 —— 需要多次用/结果要稳定时, 及早物化一次
var result = FilterAbove(data, 10).ToList();   // 物化成 List, 方法体只执行一次
foreach (var x in result) { /* 第一次 */ }
foreach (var x in result) { /* 第二次: 用的是已算好的 List, 不再重跑方法体 */ }

这套做法的精髓,是把"需要立即发生的事"(参数校验)和"可以延迟发生的事"(逐个产出元素)分到两个方法里,让前者随调用立即执行、后者保持惰性。外层普通方法没有 yield,所以它的方法体在调用那一刻就老老实实跑完——校验该抛的异常当场就抛,调用处的 try-catch 稳稳接住;内层迭代器才享受延迟执行的好处(不必一次性把整个结果算出来、能处理无限序列、省内存)。而对"重复遍历会重跑方法体"这个雷,正解是认清"延迟序列每次遍历都重新计算"这个事实,在需要结果稳定或要用多次时,用 ToList()/ToArray() 主动物化一次。不是放弃 yield 的好处,而是清楚地区分"声明计算"和"触发计算",让每件事在该发生的时候发生。

【用 yield/延迟序列, 我现在认死的几条】

1. 带 yield 的方法, 调用时方法体一行不执行, 只返回迭代器

2. 方法体(含参数校验)延迟到第一次遍历(MoveNext)才执行

3. 要参数校验"立即"生效: 拆成 外层普通方法(校验) + 内层迭代器方法(yield)

4. 延迟序列每次遍历都【重新执行方法体】—— 副作用/查询会重复

5. 需要结果稳定或多次使用: 及早 ToList()/ToArray() 物化一次

6. 迭代器依赖的资源(连接/文件), 要保证遍历期间它还活着

7. 异常会在"遍历时"才抛, try-catch 要包住真正遍历的地方

第三件事:其他"延迟执行/惰性求值咬人"的同类坑

顺着"声明了不等于执行了、延迟到消费时才算"这条线,我把 C# 里同类的坑都排查了一遍,它们都源于"把一个'惰性的、待执行的'东西,当成了'已经算好的结果'":

第一个,LINQ 查询的延迟执行var q = list.Where(x => x > 10); 这一行不执行任何过滤,只是构建查询;直到你遍历 q 或 .ToList() 才真正跑。期间若 list 变了,结果也跟着变(查询用的是遍历那一刻的 list)。

第二个,在 foreach 里多次枚举同一个 LINQ/yield 序列。每次 foreach 都重新执行整条查询链,若链里有 .Select(x => ExpensiveCall(x)),那个昂贵调用会被执行 N 遍。

第三个,闭包捕获的变量在延迟执行时才取值。lambda 捕获的是变量本身,延迟执行到真正调用时才读它的当前值,而非定义 lambda 那一刻的值——经典的循环变量捕获坑。

第四个,Task 没 await 就以为执行完了var t = DoAsync(); 启动了任务但不等它;若紧接着用它的结果或假设副作用已生效,就会拿到没完成的状态——异步也是一种"发起≠完成"。

第四件事:立即执行 vs 延迟执行——什么时候用哪个

我把"立即执行的普通方法"和"延迟执行的迭代器/查询"摆在一起对比,核心看"执行时机、能否处理无限/超大序列、重复遍历的代价":

维度 普通方法(立即执行) yield 迭代器 / LINQ(延迟执行)
方法体何时执行 调用那一刻立即全部执行 调用时不执行, 遍历(MoveNext)时才逐段执行
参数校验异常何时抛 调用时当场抛, try-catch 接得住 第一次遍历时才抛, 可能逃过调用处 try-catch
能否处理无限/超大序列 不能, 要先全算出来占满内存 能, 按需逐个产出, 省内存
重复遍历的代价 结果已算好, 多次用无额外代价 每次遍历都重跑方法体, 副作用/查询重复
结果稳定性 返回时已固定 随依赖的数据源变化而变(遍历时才取)

看清这张表,选型和防坑就有谱了:要立即生效的事(参数校验、立即拿到固定结果)用普通方法或及早物化;要省内存、处理超大/无限序列、按需产出,用延迟执行,但要牢记"每次遍历都重跑、异常在遍历时才抛"。我这次踩坑,就是误把一个延迟执行的迭代器方法,当成了立即执行的普通方法,以为校验会在调用时跑。两者各有适用场景,关键是要清楚手里拿的到底是哪一种。

第五件事:我曾经对 yield/延迟执行想当然的几个误区

这次事故也把我对 yield 和延迟执行的一堆"想当然"照了个底朝天:

我以为 实际上
调用带 yield 的方法就执行了方法体 只返回一个迭代器, 方法体一行没跑
方法第一行的参数校验在调用时执行 延迟到第一次遍历才执行, 异常也那时才抛
包住调用的 try-catch 能接住校验异常 异常在遍历时抛, 若遍历不在 try 内就接不住
遍历两次返回值是用同一份算好的结果 每次遍历都从头重跑方法体, 副作用/查询执行多遍
返回的序列内容是调用那一刻固定的 遍历时才取值, 期间数据源变了结果就变

这些误区的根子是同一个:我把"声明一个计算"和"执行这个计算"当成了同一件事,而 yield/LINQ 这类惰性机制,恰恰把这两者在时间上彻底分开了。当我写下 FilterAbove(null, 10) 时,我以为我"做了"过滤(并触发了校验),其实我只是"描述了"一个待做的过滤;真正""这件事、连同它的校验、副作用、异常,全被推迟到了"有人来消费"的那一刻。很多延迟执行的坑,都源于没分清"我描述了它"和"它发生了"。

第六件事:写迭代器、用延迟序列时,我现在的自检习惯

现在每当我写带 yield 的方法、或用 LINQ 延迟序列、排查"异常没在该抛时抛、副作用跑了多遍",我都会先按这张图问自己:

这张图的精髓,是"先认清手里的是'待执行的配方'还是'算好的结果';是配方就分清什么该立即做、什么可以延迟做"设计就把立即生效的事(校验)拆进外层普通方法、延迟的事留在内层迭代器、要多次用就及早物化、排查就先确认异常/副作用是不是因为延迟执行而推迟或重复了这套习惯,让我从"调用方法就等于执行了它"变成了"先分清这是声明计算还是触发计算"——核心始终是:C# 的 yield return 会让编译器把整个方法体改写成一个状态机,调用该方法时方法体一行都不执行、只返回一个迭代器对象,方法体里的代码(包括放在最开头的参数校验)要等到有人真正遍历(MoveNext)这个迭代器时才逐段执行——这就是延迟执行/惰性求值,它把"计算的定义"和"计算的触发"在时间上彻底分开了;由此带来三个必须警惕的后果:一是参数校验的异常会推迟到遍历时才抛、从而可能逃过包在调用处的 try-catch,二是每次重新遍历都会从头重跑整个方法体、里面的副作用和查询会执行多遍,三是方法依赖的资源若在遍历时已释放就会出错;正解是把需要立即发生的事(参数校验)拆到一个不含 yield 的外层普通方法里让它随调用立即执行、把延迟产出留在内层私有迭代器方法里,并在需要结果稳定或多次使用时用 ToList/ToArray 主动物化一次。

我立下的几条规矩

这场"校验异常没在该抛时抛"的事故,换来了我写 C# 迭代器、用延迟序列时,刻进骨子里的几条铁律:

  1. 带 yield 的方法,调用时方法体一行不执行,只返回迭代器;方法体延迟到遍历时才跑。
  2. 参数校验要"立即"生效,就拆成:外层普通方法(校验)+ 内层私有迭代器方法(yield)。
  3. 延迟序列每次遍历都重跑方法体,副作用和查询会重复执行。
  4. 需要结果稳定或要用多次,及早 ToList()/ToArray() 物化一次。
  5. 异常在"遍历时"才抛,try-catch 要确实包住真正遍历的代码。
  6. 迭代器依赖的连接/文件等资源,要保证整个遍历期间它还活着。
  7. 分清手里拿的是"待执行的配方"还是"算好的结果",再决定怎么用。

附:我现在写迭代器方法的"外层校验 + 内层 yield"骨架

这是我现在写 C# 迭代器方法固定套的骨架——把这次踩坑的教训(立即校验拆外层、延迟产出留内层、必要时物化)固化成一套结构,让"校验异常逃过 try-catch"那种坑再不会埋进代码:

public static class SafeIterators
{
    // 外层: 公开的普通方法, 不含 yield → 调用时【立即执行】, 校验当场生效
    public static IEnumerable<T> FilterBy<T>(IEnumerable<T> source, Func<T, bool> predicate)
    {
        // ① 所有需要"立即"发生的事都放这里: 参数校验、快速失败
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (predicate == null) throw new ArgumentNullException(nameof(predicate));
        // ② 校验通过后, 再返回延迟执行的内层迭代器
        return Iterator(source, predicate);
    }

    // 内层: 私有迭代器, 含 yield → 延迟执行; 参数已被外层保证合法
    private static IEnumerable<T> Iterator<T>(IEnumerable<T> source, Func<T, bool> predicate)
    {
        foreach (var item in source)        // 这里才是延迟执行的部分
            if (predicate(item))
                yield return item;          // 按需逐个产出, 省内存、可处理大序列
    }
}

// 用法 1: 立即校验生效 —— null 当场抛, try-catch 接得住
try { var q = SafeIterators.FilterBy<int>(null, x => x > 0); }
catch (ArgumentNullException) { /* ✓ 立刻接住, 不再逃逸 */ }

// 用法 2: 要多次遍历 / 结果要稳定 → 及早物化一次, 避免重跑方法体
var stable = SafeIterators.FilterBy(data, x => x > 10).ToList();
// 之后随便遍历多少次, 都用这份已算好的 List, 副作用只发生过一次

这套骨架把我这次的教训钉死在了结构里:凡是要"立即"发生的事(参数校验、快速失败)一律放进不含 yield 的外层普通方法、让它随调用当场执行;含 yield 的延迟产出退到内层私有迭代器、参数已被外层保证合法;调用方要多次用或要结果稳定,就及早 ToList() 物化一次这样,该立即抛的异常在调用现场就抛、被 try-catch 稳稳接住,该延迟的好处(省内存、处理大序列、按需产出)一个不少,而"重复遍历重跑方法体"的雷也被物化拆掉了。把"分清声明计算与触发计算、让该立即的立即发生"这个道理,沉淀成迭代器方法的固定写法,这是我对这次 null 崩溃最实在的交代——毕竟,异常该在它被引发的现场落网,而不是延迟到某个我守不住的远方。

写在最后

回头看,这场由"yield 延迟执行"引发的"校验异常逃过 try-catch"事故,真正教给我的,远不止"把参数校验拆到外层方法"这一个技巧。它让我对"当我们写下一句'做某事'的代码时,我们的直觉默认它'立刻就做了';但有一整类机制(惰性求值、延迟执行、异步、未来式),它们让'写下'只意味着'描述了一个待做的计算',而'真正去做'被推迟到了未来某个'消费'的时刻——'声明'与'执行'之间,被悄悄插入了一段时间差,而我们的直觉对这段时间差毫无察觉",有了一次刻骨的体会。我栽跟头,是因为我把"声明了一个计算(调用了那个方法)"等同于"执行了这个计算(跑了方法体、做了校验)"——我的直觉来自普通方法:调用 = 执行,天经地义;我没意识到,yield 这个小小的关键字,已经悄悄把整个方法变成了"惰性的":我调用它,只是拿到了一张"怎么算"的配方,而不是"算好的菜";配方上写的每一步——包括第一步"检查食材是否为空"——都要等真正下厨(遍历)时才执行;于是我以为在"调用时"就该抛的校验异常,实际推迟到了"遍历时"才抛,而我守在"调用处"的那张 try-catch 网,早已不在异常真正落下的现场了这让我领悟到一个关于"声明与执行、描述与发生"的深刻认知:在编程里(也在做事里),"我描述/安排了一件事"和"这件事真的发生了",是两件可能相隔一段时间、甚至发生在完全不同的上下文里的事;惰性求值、延迟执行、异步、回调、未来式——这些强大的机制,本质都是把"定义计算"和"触发计算"解耦,以换取按需、省资源、可组合、不阻塞的好处;但代价是,与这个计算绑定的一切——它的副作用、它抛的异常、它读取的数据、它依赖的资源——也都跟着被推迟到了"触发"的那一刻、那个上下文,而不再发生在你"定义"它的地方;谁要是还用"定义即执行"的旧直觉去看待它,就会在异常的捕获时机、副作用的执行次数、数据的取值时点上,一次次扑空这给了我一种看待"一切'延迟/异步/惰性'之事"时的清醒:每当我拿到一个序列、一个查询、一个任务、一个回调时,要先追问"我手里的,是一个'已经算好的结果',还是一个'待执行的配方'?如果是配方,它真正执行、连同它的异常和副作用,会发生在什么时候、什么地方?我需要的'立即'(校验、固定结果),是不是被推迟了"——分清"声明计算"与"触发计算",让该立即的立即发生、让延迟的好处为我所用,而不是被时间差咬伤;"区分'描述了它'和'它发生了'、看清延迟执行把异常与副作用推到了何时何地",是用对 yield、也是用对一切惰性/异步机制的关键认清带 yield 的方法调用时方法体不执行、校验异常延迟到遍历才抛、要把立即生效的事拆到外层——这,是我用一次校验异常逃过 try-catch 的事故,换来的、关于 C#、也关于如何分清声明与执行的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次在一个带 yield 的方法开头写下参数校验、或拿到一个 LINQ 查询时,先想一句"这是会立即执行,还是延迟到遍历才执行?",并据此把校验拆出去、或及早物化,那我对着那个"明明包着 try-catch 却没接住异常"的堆栈发懵的大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我给接口加了限流、限定每分钟最多 600 次以为稳了,大促时下游服务还是被瞬间打爆雪崩,我反复确认限流配置数字没错、监控里平均 QPS 也没超百思不得其解,最后才发现是我用的固定窗口计数器在每分钟切换那一瞬间放进了两倍流量的深度复盘

2026-6-3 7:21:33

技术教程

我在 TypeScript 里到处用感叹号非空断言把编译器的红线消掉、它不报错我就以为安全了,结果线上照样满屏 Cannot read properties of undefined 的崩溃,排查很久才彻底想通那个感叹号根本不会在运行时做任何检查、它只是我对编译器单方面许下的一个空头承诺的深度复盘

2026-6-3 7:33:44

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索