使用 throw 和 try 语句来处理异常。 使用 throw 语句引发异常。 使用 try 语句捕获和处理在执行代码块期间可能发生的异常。
不处理异常
若不适用异常处理机制,代码运行过程中的任意异常(整数除零、文件找不到等)都会导致代码或应用程序中途退出,仅仅是提示某处发生异常。
int Zhs = 5;
for ( int z = 3 ; z >= 0 ; z-- )
{
Console . WriteLine ( $"{Zhs} ÷ {z} = {Zhs / z}" );
}
当代码运行到 z 为 0 时,中断执行。控制权返回到 VS,并提示你产生“System . DivideByZeroException:“Attempted to divide by zero(试图除以 0)。”
”。若已编译后执行,则会输出:
5 ÷ 3 = 1
5 ÷ 2 = 2
5 ÷ 1 = 5
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
at C__的引发异常和捕获处理异常.Program.Main(String[] args) in G:\Visual Studio Code\repos\C# 的引发异常和捕获处理异常\C# 的引发异常和捕获处理异常\Program.cs:line 10
并退出应用程序,关闭控制台窗口。若代码尚有未执行的部分,也被忽略了。
try 捕获异常
可以使用以下任何形式的 try 语句组合:
- try……catch…… 处理在 try 块内执行代码期间可能发生的异常
- try……finally…… 指定在控件离开 try 块时执行的代码
- try……catch……finally 作为上述两种形式的组合
try……catch……
使用 try……catch…… 语句处理在执行代码块期间可能发生的异常。将可能发生异常的代码置于 try 块中,使用 catch 子句指定要在相应的 catch 块中处理的异常的基类型:
int Zhs = 5;
for ( int z = 3 ; z >= 0 ; z-- )
{
try
{
Console . WriteLine ( $"{Zhs} ÷ {z} = {Zhs / z}" );
}
catch ( DivideByZeroException Ych0 ) { Console . WriteLine ( $"整数(定点数)不能除以零!{Ych0 . Message}" ); }
}
由于整型数的除法(整除)通常仅会发生除零异常(DivideByZeroException),catch 中仅需要处理该异常。
当可能发生多种异常时,可以使用下列方式处理异常:
- 仅使用一个 catch 处理通用异常 Exception;
- 使用多个 catch 处理每一种可能的异常(也可包括通用异常)
通用异常(Exception)
int Zhs = 5;
for ( int z = 3 ; z >= 0 ; z-- )
{
try
{
Console . WriteLine ( $"{Zhs} ÷ {z} = {Zhs / z}" );
}
catch ( Exception Ych ) { Console . WriteLine ( $"发生异常:{Ych . Message}" ); }
}
上例中的 Exception 类型的 Ych 即包容了所有的异常,发生异常即转到该 catch 处理,由于 DivideByZeroException 是 Exception 的子类,所以实际显示 DivideByZeroException 异常的 Message。
处理多个异常
使用多个 catch 块,可以为每种异常使用不同的处理程序。
string Zfc文件名 = @"C:\1234.txt";
try
{
StreamReader LD = new ( Zfc文件名 );
Console . WriteLine ( LD . ReadToEnd ( ) );
}
catch ( DirectoryNotFoundException YchMYwjj )
{
Console . WriteLine ( $"文件夹未找到,请确认路径正确:{YchMYwjj . Message}" );
}
catch ( FileNotFoundException YchMYwj )
{
Console . WriteLine ( $"文件未找到,请确认路径正确:{YchMYwj . Message}" );
}
上例使用了两个 catch 处理文件读取操作可能遇到的异常(还有很多……)。
每一个 catch 必须确保其上面的其他 catch 并未捕获该异常。否则将警告 CS0160:上一个 catch 子句已经捕获了此类型或超类型(“Exception”)的所有异常。意即,上面的异常是顶层的可能异常,依次下放,直至最底层的异常(或者不会发生的异常为止);或者两个异常无关。上述两个 catch(无关)可以互换位置。下例中的两个 catch 不能互换位置。
public class Ych自定义 : System . ArgumentException { }
public class MyClass
{
public static void Main ( )
{
try { }
catch ( Ych自定义 ) { } // CS0160 被派生的;必须是第一个 catch
catch ( System . Exception ) { } // 总的 Exception;必须是最后一个
}
}
catch 子句若不引用 Exception 类型的属性,可以忽略 Exception 变量的声明,仅指定异常类型(如上例)。特别的,可以没有异常类型的指定,即 catch ( )
,表示它省略了 Exception 的类型名,且与任意异常类型匹配,若存在这一块,该块必须是 try 的最后一个 catch 块。
如果要重新引发捕获的异常,请使用 throw 语句,如以下示例所示:
try
{
var result = Process ( -3 , 4 );
Console.WriteLine ( $"执行成功:{result}" );
}
catch ( Exception e )
{
LogError ( e , "执行失败。" );
throw;
}
备注:throw; 保留异常的原始堆栈跟踪,该跟踪存储在 Exception . StackTrace 属性中。 与此相反,throw e; 更新 e 的 StackTrace 属性。
when 异常筛选器
除了异常类型之外,还可以指定异常筛选器,该筛选器进一步检查异常并确定相应的 catch 块是否处理该异常。异常筛选器是遵循 when 关键字的布尔表达式,如以下示例所示:
string Zfc文件名 = @"C:\123\123.txt";
try
{
StreamReader LD = new ( Zfc文件名 );
Console . WriteLine ( LD . ReadToEnd ( ) );
}
catch ( IOException YchIO ) when ( YchIO is DirectoryNotFoundException || YchIO is FileNotFoundException )
{
Console . WriteLine ( $"文件未找到,请确认文件名或路径正确:{YchIO . Message}" );
}
}
上例中使用 IOException 代替前例中的 DirecotryNotFoundException 和 FileNotFoundException,并用 when 关键字指定该异常限制为“路径不存在”和“文件不存在”(反正都是文件不存在),并同时处理两个异常。
可以为 catch ( IOException YchIO )
添加任意多个 catch 子句,直至可能发生的 IO 异常都被处理了;若无法预测还有哪个异常,必须最后放置一个没有 when 关键字的 catch,以处理所有未预测到的 IO 异常。
catch 的 when 子句指定的异常类型必须是 catch 语句指定的异常类型,或者其派生类型。如果存在异常筛选器,则 catch (Exception e)
子句不需要是最后一个子句。
异步和迭代器方法中的异常
如果异步函数中发生异常,则等待函数的结果时,它会传播到函数的调用方,如以下示例所示:
public static async Task Run()
{
try
{
Task < int > ChL = ProcessAsync ( -1 );
Console . WriteLine ( "启动处理。" );
int Jg = await ChL;
Console . WriteLine ( $"结果:{Jg}。");
}
catch ( ArgumentException Ych )
{
Console . WriteLine ( $"处理失败:{Ych . Message}");
}
// 输出:
// 启动处理。
// 处理失败:输入必须是非负数。(Parameter '输入')
}
private static async Task < int > ProcessAsync ( int 输入 )
{
if ( 输入 < 0 )
{
throw new ArgumentOutOfRangeException ( nameof ( 输入 ), "输入必须是非负数。");
}
await Task . Delay ( 500 );
return 输入;
}
如果迭代器方法中发生异常,则仅当迭代器前进到下一个元素时,它才会传播到调用方。
try……finally…… 语句
在 try……finally…… 语句中,当控件离开 try 块时,将执行 finally 块。 控件可能会离开 try 块,因为:
- 正常执行
- 执行跳转语句(即 return、break、continue 或 goto)
- 从 try 块中传播异常
以下示例使用 finally 块在控件离开方法之前重置对象的状态:
public async Task HandleRequest ( int itemId, CancellationToken ct )
{
Busy = true;
try
{
await ProcessAsync ( itemId , ct );
}
finally
{
Busy = false;
}
}
还可以使用 finally 块来清理 try 块中使用的已分配资源。
备注:当资源类型实现 IDisposable 或 IAsyncDisposable 接口时,请考虑 using 语句。using 语句可确保在控件离开 using 语句时释放获取的资源。编译器将 using 语句转换为 try……finally…… 语句。
finally 块的执行取决于操作系统是否选择触发异常解除操作。未执行 finally 块的唯一情况涉及立即终止程序。例如,由于 Environment . FailFast 调用或 OverflowException 或 InvalidProgramException 异常,可能会发生此类终止。大多数操作系统在停止和卸载进程的过程中执行合理的资源清理。
try……catch……finally 语句
使用 try……catch……finally 语句来处理在执行 try 块期间可能发生的异常,并指定在控件离开 try 语句时必须执行的代码:
public async Task ProcessRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
catch (Exception e) when (e is not OperationCanceledException)
{
LogError(e, $"Failed to process request for item ID {itemId}.");
throw;
}
finally
{
Busy = false;
}
}
当 catch 块处理异常时,finally 块在执行该 catch 块后执行(即使执行 catch 块期间发生另一个异常)。有关 catch 和 finally 块的信息,请分别参阅“try……catch……”语句和“try……finally……” 语句部分。
throw 语句
throw 语句引发异常:
if ( shu形状 <= 0 )
{
throw new ArgumentOutOfRangeException ( nameof ( shu形状 ), "形状的数量必须是正数。" );
}
在 throw e; 语句中,表达式 e 的结果必须隐式转换为 System . Exception。
可以使用内置异常类,例如 ArgumentOutOfRangeException 或 InvalidOperationException。.NET 还提供了以下在某些情况下引发异常的帮助程序方法:ArgumentNullException . ThrowIfNull 和 ArgumentException . ThrowIfNullOrEmpty。还可以定义自己的派生自 System . Exception 的异常类。
在 catch 块内,可以使用 throw; 语句重新引发由 catch 块处理的异常:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
备注:throw; 保留异常的原始堆栈跟踪,该跟踪存储在 Exception . StackTrace 属性中。 与此相反,throw e; 更新 e 的 StackTrace 属性。
引发异常时,公共语言运行时 (CLR)将查找可以处理此异常的 catch 块。 如果当前执行的方法不包含此类 catch 块,则 CLR 查看调用了当前方法的方法,并以此类推遍历调用堆栈。 如果未找到 catch 块,CLR 将终止正在执行的线程。
throw 表达式
还可以将 throw 用作表达式。 这在很多情况下可能很方便,包括:
条件运算符。 以下示例使用 throw 表达式在传递的数组 args 为空时引发 ArgumentException:
string first = args . Length >= 1 ? args [ 0 ] : throw new ArgumentException ( "请提供至少一个论据。" );
null 合并运算符。以下示例使用 throw 表达式在要分配给属性的字符串为 null 时引发 ArgumentNullException:
public string Name { get => name; set => name = value ?? throw new ArgumentNullException ( paramName: nameof ( value ), message: "名称不能为 null"); }
lambda 表达式或 lambda 方法。以下示例使用 throw 表达式引发 InvalidCastException,以指示不支持转换为 DateTime 值:
DateTime ToDateTime ( IFormatProvider provider ) => throw new InvalidCastException ( "不支持转换到 DateTime。");
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。