06-异常捕获
1. 异常的概念
在 C# 中,异常是指程序在运行时发生的错误或意外情况。它们会打断正常的程序流,可能导致程序崩溃。为了提高程序的健壮性,C# 提供了异常处理机制,使得我们可以捕获并处理这些错误,确保程序在遇到异常时可以优雅地处理,而不是直接终止。
2. 错误分类
在编程中,错误可以大致分为编译错误和运行错误。这两种错误的性质不同,出现的时机和处理方式也不同。
1. 编译错误
编译错误是指在编写代码时,编译器无法将源代码转换为可执行文件时出现的错误。这类错误通常是由于语法错误或类型不匹配等问题导致的。
常见的编译错误:
- 语法错误:代码不符合语言的语法规则。例如,缺少分号、括号不匹配等。
- 类型不匹配:变量的数据类型与操作不兼容。例如,将一个字符串赋值给一个整数变量。
- 命名错误:使用了未声明的变量或方法,或者拼写错误。
- 缺少引用:使用了没有包含在项目中的库或命名空间。
编译错误示例:
int number = "Hello"; // 类型不匹配错误
在这个例子中,尝试将一个字符串赋值给整数变量,会导致编译错误。
编译错误的特点:
- 编译错误是在编译阶段发现的,编译器会直接指出错误的位置和原因。
- 由于代码无法成功编译,程序根本无法执行。
- 解决编译错误需要修正代码中的语法或逻辑问题。
2. 运行错误
运行错误是在程序成功编译并启动后,在运行过程中发生的错误,也被称之为异常。这类错误通常是由于不可预见的情况或逻辑错误导致的,如除零、空引用等。
常见的运行错误:
- 数据类型转换异常 :在转化数据类型是数据类型格式不匹配引发的错误。
- 空引用异常(NullReferenceException) :访问了未初始化的对象或空对象。
- 除零错误(DivideByZeroException) :在计算时,分母为零。
- 数组越界错误(IndexOutOfRangeException) :访问数组时,索引超出了数组的长度。
- 文件访问错误:试图访问不存在的文件或没有权限访问的文件。
运行错误示例:
int divisor = 0;
int result = 10 / divisor; // 会导致除零运行错误
在这个例子中,程序成功编译并运行,但是在运行时会由于除零操作导致 DivideByZeroException
异常。
运行错误的特点:
- 程序在编译时不会发现这些错误,因为代码的语法是正确的。
- 运行错误会在程序运行到出问题的地方时才发生,可能会导致程序崩溃。
- 通过异常捕获(
try-catch
)机制可以处理运行错误,以防止程序崩溃。
3. 编译错误与运行错误的比较
对比项 | 编译错误 | 运行错误 |
---|---|---|
发生时间 | 编译阶段 | 程序运行时 |
错误类型 | 语法错误、类型错误等 | 空引用、除零、数组越界等 |
影响 | 程序无法编译成功,无法生成可执行文件 | 程序在运行中崩溃,可能中断执行 |
处理方式 | 通过编译器提示修正代码 | 通过异常捕获(try-catch )处理或修正代码逻辑 |
例子 | 变量类型不匹配、缺少分号等 | 除零错误、空引用错误 |
3. 异常捕获
1. 异常捕获机制的基本结构
C# 中的异常捕获主要使用 try-catch-finally
结构:
try
{
// 可能抛出异常的代码
}
catch (Exception ex)
{
// 处理异常的代码
}
finally
{
// 总会执行的代码(可选)
}
- try:将可能抛出异常的代码块放在
try
语句中。 - catch:当
try
语句块中的代码抛出异常时,控制流会跳转到相应的catch
语句块中,处理异常。 - finally:不论是否发生异常,
finally
代码块都会执行,常用于清理资源。
2. 使用 catch
捕获异常
catch
可以捕获特定类型的异常,或者捕获所有类型的异常。最常见的做法是捕获 System.Exception
类或其派生类的异常。
try
{
int result = 10 / 0; // 会导致除零异常
}
catch (DivideByZeroException ex)
{
Console.WriteLine("除零错误:" + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("发生了一个异常:" + ex.Message);
}
在这个例子中,catch (DivideByZeroException)
用于捕获除零异常,而 catch (Exception)
用于捕获其他所有类型的异常。
3. 多个 catch
块
C# 支持多个 catch
块,处理不同类型的异常。处理时会根据异常的类型依次匹配,找到最适合的处理代码。
try
{
// 一些可能会抛出不同异常的代码
}
catch (NullReferenceException ex)
{
Console.WriteLine("空引用异常:" + ex.Message);
}
catch (InvalidOperationException ex)
{
Console.WriteLine("无效操作异常:" + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("其他异常:" + ex.Message);
}
4. finally
块
finally
块中代码无论是否发生异常都会执行,常用于释放资源,如关闭文件或数据库连接。
try
{
// 可能抛出异常的代码
}
catch (Exception ex)
{
Console.WriteLine("捕获的异常:" + ex.Message);
}
finally
{
Console.WriteLine("清理工作,比如关闭文件或连接");
}
即使在 try
或 catch
中有 return
,finally
块中的代码也会在返回之前执行。
5. 自定义异常
C# 允许自定义异常,通过继承 System.Exception
类实现。
public class MyCustomException : Exception
{
public MyCustomException(string message) : base(message)
{
}
}
try
{
throw new MyCustomException("这是一个自定义异常");
}
catch (MyCustomException ex)
{
Console.WriteLine(ex.Message);
}
通过自定义异常,可以更加明确地表达异常的含义,增强代码的可读性。
6. 异常再抛出
有时,处理完异常后,可能需要将异常再抛出以便上层调用者处理。可以通过 throw
语句重新抛出捕获的异常:
try
{
// 可能抛出异常的代码
}
catch (Exception ex)
{
Console.WriteLine("处理了部分异常逻辑");
throw; // 重新抛出异常
}
4. 总结
- 编译错误:发生在编译时,通常由语法或类型问题引发,必须修正才能运行程序。
- 运行错误:发生在程序运行时,通常由不可预见的异常情况或逻辑错误引发,需要通过异常处理机制来应对。
- 异常捕获是 C# 中处理运行时错误的重要机制。
- 通过
try-catch-finally
,程序可以在异常发生时继续运行,避免崩溃。 - 自定义异常允许我们定义自己的异常类型,从而更好地描述特定的错误。
- finally 块常用于资源清理,即使有异常发生,它也会确保清理操作的执行。
- 在合适的情况下,可以通过
throw
再抛出异常,供上层调用者处理。