文章目录

C# 异常处理:try-catch-finally 块的使用

发布于 2026-04-05 21:59:56 · 浏览 15 次 · 评论 0 条

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 的使用,你就能写出更加健壮的程序,从容应对各种运行时错误。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文