C# 异常处理
本章将详细介绍 C# 中的异常处理机制,包括 try-catch、finally、自定义异常等。
异常概述
什么是异常?
异常是程序执行过程中发生的错误,它会中断正常的控制流。C# 提供了结构化的异常处理机制来处理这些错误。
正常流程:
开始 → 处理 → 处理 → 处理 → 结束
异常流程:
开始 → 处理 → [异常发生] → 异常处理 → 恢复或终止
常见异常类型
// System.Exception 是所有异常的基类
// 常见异常
ArgumentException // 参数无效
ArgumentNullException // 参数为空
ArgumentOutOfRangeException // 参数超出范围
InvalidOperationException // 操作无效
NotSupportedException // 操作不支持
NullReferenceException // 空引用
IndexOutOfRangeException // 索引越界
DivideByZeroException // 除零
OverflowException // 算术溢出
FormatException // 格式错误
IOException // I/O 错误
try-catch 语句
基本语法
try
{
// 可能抛出异常的代码
int result = 10 / 0; // 会抛出 DivideByZeroException
}
catch (DivideByZeroException)
{
// 处理除零异常
Console.WriteLine("不能除以零");
}
多个 catch 块
try
{
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[10]); // 索引越界
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"数组索引错误: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"其他异常: {ex.Message}");
}
异常过滤器(C# 6+)
try
{
// 可能抛出异常的代码
}
catch (ArgumentException ex) when (ex.ParamName == "id")
{
// 只处理参数名为 "id" 的 ArgumentException
Console.WriteLine($"ID 参数错误: {ex.Message}");
}
catch (ArgumentException ex)
{
// 处理其他 ArgumentException
Console.WriteLine($"参数错误: {ex.Message}");
}
捕获所有异常
try
{
// 可能抛出异常的代码
}
catch
{
// 捕获所有异常(不推荐,无法访问异常信息)
}
Exception 对象属性
try
{
// 抛出异常
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // 异常消息
Console.WriteLine(ex.StackTrace); // 堆栈跟踪
Console.WriteLine(ex.Source); // 异常来源
Console.WriteLine(ex.InnerException); // 内部异常
}
finally 语句
finally 的作用
FileStream? file = null;
try
{
file = new FileStream("test.txt", FileMode.Open);
// 读取文件
}
catch (FileNotFoundException)
{
Console.WriteLine("文件未找到");
}
finally
{
// 无论是否异常,都会执行
file?.Close(); // 确保资源被释放
}
使用 using 简化
// 使用 using 语句自动释放资源(C# 8+ 可以使用 using var)
using var file = new FileStream("test.txt", FileMode.Open);
// 文件会在 using 块结束时自动关闭
finally 中的异常
try
{
// 可能抛出异常
}
finally
{
// finally 中也可能抛出异常
// 这个异常会替代原来的异常
}
throw 语句
抛出异常
public void SetAge(int age)
{
if (age < 0 || age > 150)
{
throw new ArgumentException("年龄必须在 0-150 之间", nameof(age));
}
_age = age;
}
重新抛出异常
try
{
// 可能抛出异常
}
catch (Exception ex)
{
// 记录日志
Log(ex);
// 重新抛出,保留原始堆栈信息
throw;
}
抛出新异常(包装异常)
try
{
// 可能抛出异常
}
catch (Exception ex)
{
// 包装异常并添加新信息
throw new InvalidOperationException("操作失败", ex);
}
自定义异常
创建自定义异常
// 继承自 Exception 或其子类
public class BusinessException : Exception
{
public int ErrorCode { get; }
public BusinessException() { }
public BusinessException(string message) : base(message) { }
public BusinessException(string message, Exception inner)
: base(message, inner) { }
public BusinessException(string message, int errorCode)
: base(message)
{
ErrorCode = errorCode;
}
}
使用自定义异常
public void Withdraw(decimal amount)
{
if (amount <= 0)
{
throw new BusinessException("提现金额必须大于零", 1001);
}
if (amount > Balance)
{
throw new BusinessException("余额不足", 1002);
}
// 处理提现
}
异常的最佳实践
不要过度使用异常
// 不好的做法:使用异常处理控制流
try
{
int result = int.Parse(input);
if (result == 0)
{
// 不推荐:使用异常判断
}
}
catch
{
// 处理
}
// 好的做法:使用 TryParse
if (int.TryParse(input, out int result))
{
// 正常处理
}
异常过滤器的使用
// 根据条件处理不同的异常
try
{
await service.CallAsync();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// 处理 404
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
// 处理 401
}
catch (HttpRequestException ex)
{
// 处理其他 HTTP 错误
}
资源清理
// 方式1:try-finally
Connection? conn = null;
try
{
conn = new Connection();
conn.Open();
// 使用连接
}
finally
{
conn?.Dispose();
}
// 方式2:using 语句(推荐)
using (var conn = new Connection())
{
conn.Open();
// 使用连接
} // 自动调用 Dispose
// 方式3:C# 8+ using 声明
using var conn2 = new Connection();
conn2.Open();
// 使用连接
// 方法结束时自动Dispose
异常传播
在异步方法中传播异常
async Task AsyncMethod()
{
try
{
await Task.Run(() => throw new InvalidOperationException());
}
catch (InvalidOperationException ex)
{
// 可以在这里处理
throw; // 也可以重新抛出
}
}
// 调用
try
{
await AsyncMethod();
}
catch (InvalidOperationException ex)
{
// 异步方法中的异常会传播到这里
}
聚合异常
// 当多个任务同时失败时,会产生 AggregateException
async Task MultiTaskAsync()
{
var tasks = new[]
{
Task.Run(() => throw new Exception("错误1")),
Task.Run(() => throw new Exception("错误2")),
Task.Run(() => throw new Exception("错误3"))
};
try
{
await Task.WhenAll(tasks);
}
catch
{
// 捕获 AggregateException
var ae = (AggregateException)ExceptionDispatchInfo.Capture(
ExceptionDispatchInfo.CurrentException
).SourceException;
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}
调试技巧
异常的堆栈跟踪
try
{
Method1();
}
catch (Exception ex)
{
Console.WriteLine("堆栈跟踪:");
Console.WriteLine(ex.StackTrace);
}
void Method1() => Method2();
void Method2() => Method3();
void Method3() => throw new Exception("异常发生位置");
自定义异常筛选器日志
try
{
// 代码
}
catch (Exception ex) when (LogException(ex))
{
// 不会执行到这里
}
static bool LogException(Exception ex)
{
// 记录日志,返回 false 表示不处理异常
Console.WriteLine($"异常: {ex.Message}");
return false;
}
小结
- try-catch:捕获和处理异常
- finally:无论是否异常都执行
- throw:抛出异常
- 自定义异常:创建特定领域的异常类
- 异常过滤器:按条件处理异常
- 资源管理:using 语句自动释放资源
练习
- 实现一个计算器,捕获除零异常
- 创建自定义异常类并使用
- 实现文件读取功能,捕获多种 IO 异常
- 实现重试机制来处理暂时性失败