C#异常处理机制详解:Try-Catch-Finally与自定义异常
在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属性。 - 常规线程中:若到达线程开头仍未被处理,则终止该线程。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









