C# 异常处理:try-catch-finally 块的使用
程序运行过程中,错误随时可能发生。文件找不到、网络连接中断、数组越界……这些情况如果不做处理,会导致程序直接崩溃,用户体验极差。C# 提供了 try-catch-finally 机制,让你能优雅地捕获并处理这些异常,确保程序在遇到问题时依然可控。
1. 认识异常
异常是程序运行时发生的非正常事件。当异常发生时,程序会中断当前执行流程,转而寻找能够处理该异常的代码。如果找不到处理程序,程序最终会终止并显示错误信息。
常见的异常类型包括:
| 异常类型 | 触发场景 |
|---|---|
ArgumentNullException |
参数为 null |
ArgumentException |
参数值无效 |
IndexOutOfRangeException |
索引超出数组范围 |
FileNotFoundException |
文件不存在 |
DivideByZeroException |
除数为零 |
InvalidOperationException |
方法调用状态无效 |
2. try-catch-finally 的完整结构
try
{
// 可能抛出异常的代码放在这里
}
catch (ExceptionType1 ex)
{
// 处理 ExceptionType1 类型的异常
}
catch (ExceptionType2 ex)
{
// 处理 ExceptionType2 类型的异常
}
finally
{
// 无论是否发生异常,都会执行的清理代码
}
三个块的作用:
try块:包裹可能抛出异常的代码。catch块:捕获并处理特定类型的异常,可以有多个。finally块:执行清理工作(如关闭文件、释放资源),无论是否发生异常都会执行。
3. try 块:监控异常
将可能出错的代码放入 try 块,程序会监控这部分代码的运行状态。一旦抛出异常,程序立即跳转到匹配的 catch 块。
try
{
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[10]); // 这里会抛出 IndexOutOfRangeException
Console.WriteLine("这行不会执行"); // 因为上面的代码已抛出异常
}
如果 try 块中的代码全部正常执行完毕,程序会跳过所有 catch 块,继续执行后续代码。
4. catch 块:捕获并处理异常
当 try 块抛出异常时,程序会按顺序检查每个 catch 子句,找到匹配的类型后执行对应代码。
4.1 捕获特定类型异常
try
{
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[10]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"数组索引超出范围:{ex.Message}");
}
```
### 4.2 捕获多种异常
**按从具体到一般的顺序排列 `catch` 块**:
```csharp
try
{
int divisor = 0;
int result = 10 / divisor; // 抛出 DivideByZeroException
}
catch (DivideByZeroException ex)
{
Console.WriteLine("除数不能为零");
}
catch (Exception ex)
{
Console.WriteLine($"发生未知错误:{ex.Message}");
}
如果将 Exception 类型的 catch 块放在前面,会先匹配到它,导致更具体的异常无法被正确处理。
4.3 使用 when 条件筛选
C# 6.0 以上支持在 catch 后添加 when 条件,进一步细化捕获条件:
try
{
// 某些可能抛出异常的代码
}
catch (Exception ex) when (ex.Message.Contains("timeout"))
{
Console.WriteLine("捕获到超时相关的异常");
}
catch (Exception ex)
{
Console.WriteLine($"捕获到其他异常:{ex.Message}");
}
```
---
## 5. finally 块:确保执行清理
`finally` 块的代码**无论是否发生异常都会执行**,适合用于释放资源、关闭连接、清理临时文件等收尾工作。
```csharp
StreamReader reader = null;
try
{
reader = new StreamReader("data.txt");
string content = reader.ReadToEnd();
Console.WriteLine($"文件内容长度:{content.Length}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"文件未找到:{ex.Message}");
}
finally
{
// 确保 reader 被正确关闭
reader?.Close();
}
```
即使 `try` 块中抛出异常,或 `catch` 块中再次抛出,`finally` 块中的代码都会执行。
---
## 6. 实际应用示例
### 6.1 读取配置文件并处理异常
```csharp
try
{
string configPath = "config.json";
string jsonContent = File.ReadAllText(configPath);
var config = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonContent);
Console.WriteLine("配置加载成功");
}
catch (FileNotFoundException)
{
Console.WriteLine("错误:配置文件不存在,将使用默认配置");
// 使用默认配置继续执行
}
catch (Newtonsoft.Json.JsonException ex)
{
Console.WriteLine($"错误:配置文件格式错误 - {ex.Message}");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("错误:没有读取配置文件的权限");
}
catch (Exception ex)
{
Console.WriteLine($"未知错误:{ex.Message}");
}
```
### 6.2 网络请求的异常处理
```csharp
public async Task<string> FetchDataAsync(string url)
{
HttpClient client = new HttpClient();
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode(); // 如果状态码不是 2xx,抛出异常
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"网络请求失败:{ex.Message}");
return null;
}
finally
{
client.Dispose(); // 释放 HttpClient 资源
}
}
7. 最佳实践
7.1 不要吞掉异常
// 反模式:这样写会隐藏问题
catch (Exception ex)
{
// 什么都不做,异常被悄悄吞掉了
}
正确做法:
catch (Exception ex)
{
// 至少记录日志
Logger.Error(ex, "操作失败");
// 或者重新抛出,让调用方知道发生了错误
throw;
}
7.2 使用 using 语句释放资源
对于实现了 IDisposable 接口的对象,推荐使用 using 语句,自动调用 Dispose() 方法:
try
{
using (var reader = new StreamReader("data.txt"))
{
string content = reader.ReadToEnd();
}
} // reader.Dispose() 会自动调用,无需手动写 finally 块
catch (Exception ex)
{
Console.WriteLine($"读取文件时出错:{ex.Message}");
}
```
**7.3 自定义异常**
对于业务逻辑错误,可以创建自定义异常类:
```csharp
public class InsufficientBalanceException : Exception
{
public decimal CurrentBalance { get; }
public decimal RequestedAmount { get; }
public InsufficientBalanceException(decimal current, decimal requested)
: base($"余额不足:当前余额 {current}, 需要 {requested}")
{
CurrentBalance = current;
RequestedAmount = requested;
}
}
调用时:
void Withdraw(decimal amount)
{
decimal currentBalance = GetBalance();
if (amount > currentBalance)
{
throw new InsufficientBalanceException(currentBalance, amount);
}
// 执行取款逻辑
}
8. 异常处理的执行流程
graph TD
A[开始执行 try 块] --> B{是否抛出异常?}
B -->|否| C[跳过所有 catch 块]
B -->|是| D[查找匹配的 catch 块]
D --> E{找到匹配?}
E -->|是| F[执行对应 catch 块]
E -->|否| G[向上层调用栈查找]
F --> H{存在 finally 块?}
C --> H
G -->|找到| D
G -->|未找到| I[程序终止]
H --> J[执行 finally 块]
J --> K[继续执行 finally 后的代码]
I --> L[显示未处理异常]
style A fill:#e1f5fe
style K fill:#c8e6c9
style L fill:#ffcdd2
9. 总结
异常处理的核心原则是:预测可能出错的地方,做好应对方案,确保程序优雅降级。
- 使用
try包裹风险代码 - 使用
catch针对性地处理不同异常 - 使用
finally确保资源释放 - 避免吞掉异常,至少记录日志
- 优先使用
using语句管理资源
掌握了 try-catch-finally 的使用,你就能写出更加健壮的程序,从容应对各种运行时错误。

暂无评论,快来抢沙发吧!