C#异常处理机制详解:Try-Catch-Finally与自定义异常

拾光
拾光
发布时间:2026-10-13 | 6人浏览过
原创

在C#开发中,异常处理是保障程序稳定性的核心机制。本文详细讲解如何利用try-catch-finally结构捕获运行时错误,遵循从具体到通用的catch块排序原则,并通过代码实例演示如何在finally块中安全释放资源,最后介绍创建自定义异常类的标准方法。

C#异常处理机制概述

C#的异常处理功能旨在处理程序运行时出现的意外情况,如除零操作、文件未找到或内存访问违规等。公共语言运行时(CLR)、.NET Framework库以及应用程序代码均可生成异常。当异常发生时,CLR会展开调用堆栈,查找能够处理该特定异常类型的catch块;若找到,则执行相应逻辑;若在整个调用堆栈中均未找到合适的处理程序,进程将被终止并向用户显示错误信息。

异常处理主要依靠以下四个关键字协作完成:

  • try:包裹可能引发异常的代码块。
  • catch:定义异常处理程序,用于捕获特定类型的异常。
  • finally:无论是否发生异常,其中的代码块都会被执行,常用于清理资源(如关闭文件流、数据库连接)。
  • throw:用于显式地抛出异常对象。

第1步:基本异常捕获示例

以下是处理除零错误的典型场景。在下面的代码中,SafeDivision方法在被零除时抛出一个System.DivideByZeroException。如果没有异常处理机制,程序将直接崩溃并报错。

class ExceptionTest
{
  static double SafeDivision(double x, double y)
  {
    if (y == 0)
      throw new System.DivideByZeroException();
    return x / y;
  }
  
  static void Main()
  {
    // 测试输入数据
    double a = 98, b = 0;
    double result = 0;

    try
    {
      result = SafeDivision(a, b);
      Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
    }
    catch (DivideByZeroException e)
    {
      Console.WriteLine("Attempted divide by zero.");
    }
  }
}

异常对象的特性与使用规则

理解异常对象的特性有助于编写更健壮的程序。所有异常类型最终都由System.Exception派生而来,它们包含了错误的详细信息,如调用堆栈状态和文本说明。除非确定能修复问题并使应用恢复到已知状态,否则应避免捕捉通用异常。catch块中定义的异常变量可用于获取具体的错误上下文。

第2步:创建与抛出自定义异常

为了区分不同的业务错误或提供更详细的诊断信息,开发者可以派生自Exception类来创建自定义异常。例如,创建一个记录特定日志错误的异常类:

class CustomException : Exception
{
  public CustomException(string message)
  {
    // 初始化基类属性
    base.Message = message;
  }
}

private static void TestThrow()
{
  CustomException ex = new CustomException("Custom exception in TestThrow()");
  throw ex; // 主动抛出异常
}

一旦抛出异常,运行时会检查当前语句是否在try块内。如果在,则匹配关联的catch块。

第3步:捕获并处理自定义异常

使用catch关键字指定异常类型,以处理由上一步触发的异常:

static void TestCatch()
{
  try
  {
    TestThrow();
  }
  catch (CustomException ex)
  {
    System.Console.WriteLine(ex.ToString());
  }
}

高级异常处理策略

第4步:多Catch块与排序规则

一个try块可以对应多个catch块。运行时将执行第一个能够处理该异常的catch语句,后续的catch块将被忽略。因此,必须按照**从最具体(派生程度最高)到最不具体**的顺序排列这些块,否则较具体的异常将无法被正确捕获。

static void TestCatch2()
{
  System.IO.StreamWriter sw = null;
  try
  {
    sw = new System.IO.StreamWriter(@"C:\test\test.txt");
    sw.WriteLine("Hello");
  }
  // 先捕获更具体的 FileNotFoundException
  catch (System.IO.FileNotFoundException ex)
  {
    System.Console.WriteLine("File not found: " + ex.ToString()); 
  }
  // 再捕获更通用的 IOException
  catch (System.IO.IOException ex)
  {
    System.Console.WriteLine("IO error occurred: " + ex.ToString()); 
  }
  finally 
  {
    if (sw != null) sw.Close();
  }
  System.Console.WriteLine("Done"); 
}

第5步:利用Finally块确保资源释放

即使try块中发生了异常,finally块中的代码也必然会被执行。这是清理外部资源(如文件句柄、网络连接、图形对象)的关键位置,以防止资源泄漏或状态锁定。

static void TestFinally()
{
  System.IO.FileStream file = null;
  System.IO.FileInfo fileInfo = new System.IO.FileInfo(@"C:\file.txt");

  try
  {
    file = fileInfo.OpenWrite();
    file.WriteByte(0xF);
  }
  finally
  {
    // 确保在任何情况下都关闭文件
    if (file != null)
    {
      file.Close();
    }
  }

  // 此时文件已被正确释放,可重新打开
  try
  {
    file = fileInfo.OpenWrite();
    System.Console.WriteLine("OpenWrite() succeeded");
  }
  catch (System.IO.IOException)
  {
    System.Console.WriteLine("OpenWrite() failed");
  }
}

如果省略了finally中的Close()调用,且在try中发生异常,随后的代码尝试再次打开同一文件时将失败,因为该文件仍处于锁定状态。

未捕获异常的默认行为

如果在整个调用堆栈中均未找到兼容的catch块,程序将根据异常发生的上下文采取不同措施:

  • 析构函数中:中止该析构函数并调用基类的析构函数(如果有)。
  • 静态构造函数或字段初始值设定项中:引发一个TypeInitializationException,并将原始异常分配给其InnerException属性。
  • 常规线程中:若到达线程开头仍未被处理,则终止该线程。
以上就是C#中简单且标准的异常引发与处理操作的详细内容,更多关于C#编程技巧的资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表