使用 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。");

兔子码农
4 声望1 粉丝

一个酒晕子