C# 试题 19:异常处理
0361 以下代码输出什么?
难度: 实战
try
{
throw new InvalidOperationException("A");
}
catch (Exception ex) when (ex.Message == "B")
{
Console.Write("filter");
}
catch (Exception)
{
Console.Write("fallback");
}
- A.
filter - B.
fallback,过滤器为 false 时继续匹配后续 catch - C. 抛异常
- D. 什么都不输出
查看答案与解析
正确答案B
解析: when 过滤器为 false 时不进入该 catch,异常继续与后续 catch 子句匹配,最终落入兜底 catch 输出 fallback。过滤器是”匹配条件”,不是”进入后再判断”。
关键点: 过滤器 false 不会吞异常,会继续找下一个 catch。
0362 以下代码输出什么?
难度: 进阶
static bool Boom() => throw new Exception("filter boom");
try
{
throw new Exception("original");
}
catch (Exception) when (Boom())
{
Console.Write("caught");
}
catch (Exception)
{
Console.Write("fallback");
}
- A.
caught - B. 抛
filter boom - C.
fallback,过滤器求值中抛出的异常被忽略,视为 false - D. 抛
original
查看答案与解析
正确答案C
解析: 异常过滤器求值过程中抛出的异常会被运行时吞掉并当作 false 处理,不会向外传播;随后匹配到兜底 catch 输出 fallback。因此不要在过滤器里写可能抛异常的代码,异常会无声消失。
关键点: 过滤器内抛异常 = 被忽略 = 当作 false。
0363 关于以下两种重新抛出的写法,哪项正确?
难度: 实战
static void M()
{
try
{
throw new InvalidOperationException("boom");
}
catch (Exception ex)
{
throw ex; // ①
// throw; // ②
}
}
- A. 两者都保留原始堆栈
- B. ① 重置堆栈(从 catch 内的 throw 重新开始),② 保留原始堆栈
- C. ① 保留原始堆栈,② 重置堆栈
- D. 两者都重置堆栈
查看答案与解析
正确答案B
解析: throw ex 抛出的是同一个异常对象,但堆栈跟踪会被重置为”从当前 throw 语句开始”,丢失原始抛出点;throw;(裸 rethrow)不创建新跟踪,完整保留原始堆栈与内部异常链。日志分析时 throw ex 是经典的误导源。
关键点: 重新抛出用 throw;,throw ex 会毁掉堆栈跟踪。
0364 以下代码输出什么?
难度: 实战
try
{
try
{
throw new InvalidOperationException("inner");
}
catch (Exception ex)
{
throw new ApplicationException("wrap", ex);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException?.GetType().Name);
}
- A.
inner、ApplicationException - B.
wrap、ApplicationException - C.
wrap、InvalidOperationException,包装异常通过InnerException保留原始异常 - D. 编译失败
查看答案与解析
正确答案C
解析: 内层 catch 用 new ApplicationException("wrap", ex) 包装,外层拿到的是包装异常:Message 是 wrap,InnerException 是原来的 InvalidOperationException。包装语义是”保留原因、替换表象”,排查问题时必须沿 InnerException 链下钻。
关键点: 包装异常不丢原始异常,原始异常在 InnerException 里。
0365 以下代码运行结果是什么?
难度: 进阶
try
{
throw new InvalidOperationException("original");
}
finally
{
throw new FormatException("finally");
}
- A. 抛
InvalidOperationException - B. 同时抛两个异常
- C. 编译失败
- D. 抛
FormatException,finally 中抛出的异常覆盖正在传播的原始异常
查看答案与解析
正确答案D
解析: 异常传播途中执行 finally 时,若 finally 又抛出异常,后者的传播优先级更高,原始异常直接丢失(不会合并为 AggregateException)。finally 里抛异常是”吞掉原异常”的高危行为,应避免或就地 try/catch。
关键点: finally 抛异常会覆盖原始异常,原始异常信息丢失。
0366 为什么不应把异常用于正常的控制流?
难度: 进阶
- A. 异常用于预期分支会带来不必要的栈展开开销、干扰调试与日志,并掩盖真实语义
- B. 异常与
return性能完全相同 - C. 异常只能用于 I/O 场景
- D. try/catch 必须捕获所有异常
查看答案与解析
正确答案A
解析: 异常是为”异常情况”设计的:抛出涉及栈展开、SEH 记录与堆栈跟踪构造,成本远高于分支判断;把”用户输入非法”等预期情况做成异常,会让调用方语义不清、调试器频繁中断。预期分支应走 TryXxx/结果类型等正常路径。
关键点: 异常开销在抛出路径,且用异常表达预期分支是反模式。
0367 以下代码能否编译?
难度: 实战
try
{
throw new FormatException();
}
catch (Exception)
{
Console.Write("base");
}
catch (FormatException)
{
Console.Write("derived");
}
- A. 编译通过,输出
base - B. 编译失败,派生类型的 catch 必须放在基类 catch 之前
- C. 编译通过,输出
derived - D. 编译通过,输出
basederived
查看答案与解析
正确答案B
解析: catch (Exception) 能匹配所有异常,后面的 catch (FormatException) 永远不可达,编译器报 CS0160。catch 子句按顺序匹配,派生类型在前、基类在后是硬性要求;先写基类 catch 是编译错误而不是运行时行为。
关键点: catch 顺序:具体在前、基类在后,否则编译失败。
0368 关于自定义异常类型,哪项实践正确?
难度: 进阶
- A. 应继承
ApplicationException - B. 任何类型都可以作为异常抛出
- C. 直接继承
Exception(或语义更具体的异常类),类名以Exception结尾,并提供常见构造函数 - D. 自定义异常不能包含自定义属性
查看答案与解析
正确答案C
解析: 早期指南建议用 ApplicationException 作为基类,该建议早已废弃,自定义异常应直接继承 Exception 或更具体的异常;类名以 Exception 结尾便于识别;提供无参、message、message+inner 等构造函数是惯例。C# 只允许抛出 Exception 派生类型。
关键点: 基类用 Exception,别再用 ApplicationException 旧建议。
0369 以下代码输出什么?
难度: 实战
class BaseException : Exception { }
class DerivedException : BaseException { }
try
{
throw new DerivedException();
}
catch (BaseException)
{
Console.Write("base");
}
catch (DerivedException)
{
Console.Write("derived");
}
- A.
derived - B.
basederived - C. 编译失败
- D.
base,第一个能处理该异常的 catch 生效
查看答案与解析
正确答案D
解析: catch 匹配是”第一个能处理”而非”最精确”:DerivedException 是 BaseException 的子类,先出现的 catch (BaseException) 直接捕获,后面的 catch (DerivedException) 永远轮不到(此处未报错是因为派生 catch 顺序合法但不可达)。这与某些语言的”最近匹配”直觉相反。
关键点: catch 按书写顺序取第一个能处理的,不取最具体的。
0370 以下代码输出什么?
难度: 进阶
class R : IDisposable
{
public void Dispose() => throw new FormatException("dispose");
}
try
{
using var r = new R();
throw new InvalidOperationException("body");
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType().Name);
}
- A.
FormatException,using 展开为 try/finally,Dispose 的异常覆盖 try 块异常 - B.
InvalidOperationException - C.
AggregateException - D.
ObjectDisposedException
查看答案与解析
正确答案A
解析: using 等价于 try/finally 中调用 Dispose();try 块异常传播途中 finally 里 Dispose 又抛异常,后者覆盖前者,catch 拿到 FormatException。这正是”Dispose 不应抛异常”原则的由来——它会让业务异常消失。
关键点: Dispose 抛异常会吞掉 try 块的原始异常。
0371 以下代码输出什么?
难度: 实战
static int M()
{
try
{
return 1;
}
finally
{
Console.Write("f");
}
}
Console.WriteLine(M());
- A.
1f - B.
1 - C.
f1,finally 在返回值生效前执行 - D. 编译失败
查看答案与解析
正确答案C
解析: 方法返回前必须先执行 finally:先输出 f,再返回 1 由外层输出,结果 f1。finally 的执行时机是”退出 try 块之前”,包括 return 路径,这是”finally 一定执行”的语义基础(进程终止等极端情况除外)。
关键点: return 之前 finally 先跑,别假设 finally 在返回值之后。
0372 以下代码输出什么?
难度: 进阶
static int M()
{
try
{
throw new Exception();
}
catch
{
return 2;
}
finally
{
Console.Write("f");
}
}
Console.WriteLine(M());
- A.
f2,catch 中 return 同样先执行 finally - B.
2f - C.
2 - D. 编译失败
查看答案与解析
正确答案A
解析: catch 里的 return 2 也会先触发 finally:输出 f 后方法返回 2,外层输出,结果 f2。finally 覆盖 try/catch 的所有退出路径(return、throw、正常结束)。
关键点: finally 对 return 与 throw 一视同仁,先清理后返回。
0373 以下代码能否编译?
难度: 实战
static int M()
{
try { }
finally
{
return 1;
}
}
- A. 编译通过,返回
1 - B. 编译通过,运行抛异常
- C. 编译通过,返回 0
- D. 编译失败,控制流不能从 finally 块中离开
查看答案与解析
正确答案D
解析: return(以及 break/continue/goto)在 finally 中是被禁止的,报 CS0157——它会吞掉尚未传播的异常并破坏清理语义。finally 里只能做清理,结果值必须在 try/catch 里决定。
关键点: finally 中不能 return,这是编译期规则。
0374 以下代码输出什么?
难度: 实战
public class OrderException : Exception
{
public int OrderId { get; }
public OrderException(int orderId)
: base($"Order {orderId} failed") => OrderId = orderId;
}
try
{
throw new OrderException(42);
}
catch (OrderException ex)
{
Console.WriteLine(ex.OrderId);
Console.WriteLine(ex.Message);
}
- A.
0、Exception of type 'OrderException' was thrown. - B.
42、Order 42 failed - C. 编译失败
- D.
42、Order failed
查看答案与解析
正确答案B
解析: 自定义异常通过构造函数链把业务数据写入属性、把可读信息写入 Message:抛出 OrderException(42) 后,OrderId 为 42,Message 为 Order 42 failed。业务上下文应存在异常对象自身,而不是让调用方去猜。
关键点: 自定义异常用属性携带上下文,用 Message 提供可读信息。
0375 以下代码输出什么?
难度: 进阶
try
{
try
{
throw new InvalidOperationException("x");
}
catch (Exception) when (false)
{
Console.Write("filtered");
}
}
catch (Exception ex)
{
Console.Write(ex.GetType().Name);
}
- A.
InvalidOperationException,过滤器为 false 时异常继续向外传播 - B.
filtered - C.
Exception - D. 什么都不输出
查看答案与解析
正确答案A
解析: 内层 catch 的过滤器恒为 false,等于”这个 catch 不存在”,异常继续向上传播,被外层 catch 捕获,输出异常类型名 InvalidOperationException。过滤器 false 不吞异常——吞掉异常的只能是真正进入的 catch 块。
关键点: 过滤器 false = 该 catch 不匹配,异常继续传播。
0376 关于异常的性能开销,哪项正确?
难度: 进阶
- A. try/catch 块本身开销巨大,应尽量避免使用
- B. 未抛异常时 try/catch 几乎零成本;昂贵的是抛出、栈展开与堆栈跟踪构造
- C. 异常抛出后会被自动压缩
- D. 异常对象总是分配在栈上
查看答案与解析
正确答案B
解析: 现代 .NET 中,进入/退出 try 块在未抛异常时几乎没有运行时开销(异常表在 JIT 侧处理);真正的成本集中在 throw 之后的栈展开与 StackTrace 构造。因此”try 要省着用”不准确,该省的是”抛”本身。
关键点: 贵在抛不在 try;高频预期路径用 TryXxx 避免抛。
0377 以下代码输出什么?
难度: 实战
class R : IDisposable
{
public R(string name) => Name = name;
public string Name { get; }
public void Dispose() => Console.Write(Name);
}
using var a = new R("a");
using var b = new R("b");
- A.
ab - B. 编译失败
- C.
ba,using 声明按相反顺序释放 - D. 什么都不输出
查看答案与解析
正确答案C
解析: 多个 using 声明按”后进先出”释放:b 先 Dispose,再 a,输出 ba。这与嵌套 using 语句块的语义一致。依赖释放顺序时(如日志、锁),这个方向最容易记反。
关键点: 多 using 声明的释放顺序与声明顺序相反。
0378 以下代码运行结果是什么?
难度: 实战
class R : IDisposable
{
private bool _disposed;
public void DoWork()
{
if (_disposed) throw new ObjectDisposedException(nameof(R));
Console.Write("work");
}
public void Dispose() => _disposed = true;
}
var r = new R();
using (r)
{
}
r.DoWork();
- A. 输出
work - B. 编译失败,using 后的变量不可用
- C. 运行抛
NullReferenceException - D. 运行抛
ObjectDisposedException,using 只负责释放,不禁止块外继续使用变量
查看答案与解析
正确答案D
解析: using (r) 结束后 r 仍然可访问——using 只是在 finally 里调用 Dispose(),不改变变量作用域;再次 DoWork() 由类型自己的已释放检查抛 ObjectDisposedException。“using 之后的变量不可用”是错误直觉,资源已释放但引用还在。
关键点: using 释放资源但不禁用变量;释放后调用由对象自检抛异常。
0379 以下代码输出什么?
难度: 进阶
static Task Boom()
{
return Task.Run(() => throw new InvalidOperationException("x"));
}
try
{
Boom().Wait();
}
catch (Exception ex)
{
Console.Write(ex.GetType().Name);
}
- A.
AggregateException,同步等待失败的任务会把异常包装起来 - B.
InvalidOperationException - C.
TaskCanceledException - D. 编译失败
查看答案与解析
正确答案A
解析: Task.Wait() 对失败任务抛的是 AggregateException(内含原始异常);只有 await 才会解包出第一个原始异常。所以”任务异常 = 原异常”的直觉只对 await 成立,同步 Wait()/Result 一律先看到 AggregateException。
关键点: Wait/Result 抛 AggregateException,await 才解包。
0380 以下代码输出什么?
难度: 实战
static void M()
{
try
{
try
{
throw new InvalidOperationException("x");
}
finally
{
Console.Write("inner-finally;");
}
}
catch (Exception) when (true)
{
Console.Write("caught;");
}
finally
{
Console.Write("outer-finally;");
}
}
M();
- A.
caught;inner-finally;outer-finally; - B.
inner-finally;caught;outer-finally;,异常展开时先执行内层 finally,再匹配外层 catch,最后执行外层 finally - C.
inner-finally;outer-finally;caught; - D. 编译失败
查看答案与解析
正确答案B
解析: 内层 throw 后:① 展开途中先执行内层 finally(inner-finally;);② 异常到达外层,求值过滤器并进入 catch(caught;);③ 退出 try 前执行外层 finally(outer-finally;)。finally 在栈展开时按”由内到外”执行,catch 匹配发生在每层 finally 之后。
关键点: 展开顺序:内层 finally → 外层 catch → 外层 finally。