一、为什么需要关注异常处理

在开发过程中,程序难免会遇到各种意外情况,比如文件读取失败、网络连接超时、数据库查询异常等。如果这些异常没有被妥善处理,轻则导致功能失效,重则可能让整个程序崩溃。C# 提供了强大的异常处理机制,但很多开发者往往只是简单用 try-catch 包裹一下,甚至直接忽略异常,这显然是不够的。

举个例子,假设我们有一个读取配置文件的方法:

// 技术栈:C# (.NET Core)
public string ReadConfigFile(string filePath)
{
    // 直接读取文件,没有任何异常处理
    return File.ReadAllText(filePath);
}

如果 filePath 不存在,或者程序没有权限访问该文件,就会抛出 IOExceptionUnauthorizedAccessException,导致程序中断。显然,这样的代码是不健壮的。

二、C# 默认异常处理机制

C# 的异常处理主要依赖 try-catch-finally 结构,这是大多数开发者最熟悉的模式。它的基本用法如下:

// 技术栈:C# (.NET Core)
try
{
    // 可能抛出异常的代码
    var result = SomeRiskyOperation();
    Console.WriteLine(result);
}
catch (IOException ex)
{
    // 捕获特定异常(如文件IO异常)
    Console.WriteLine($"文件操作失败:{ex.Message}");
}
catch (Exception ex)
{
    // 捕获所有未处理的异常
    Console.WriteLine($"发生未知错误:{ex.Message}");
}
finally
{
    // 无论是否发生异常,都会执行的代码
    Console.WriteLine("清理资源...");
}

2.1 异常捕获的优先级

C# 的 catch 块是按照从上到下的顺序匹配的,所以应该先捕获具体的异常,再捕获更通用的异常。例如:

try
{
    // 可能抛出多种异常
    ProcessData();
}
catch (InvalidOperationException ex)
{
    // 先捕获特定异常
    Console.WriteLine("操作无效:" + ex.Message);
}
catch (Exception ex)
{
    // 最后捕获通用异常
    Console.WriteLine("其他错误:" + ex.Message);
}

如果顺序反了,Exception 会先捕获所有异常,导致更具体的 catch 块永远不会执行。

三、增强异常处理的实用技巧

3.1 使用 when 关键字进行条件捕获

C# 6.0 引入了 when 关键字,可以在 catch 块中增加额外条件:

try
{
    // 模拟数据库操作
    var data = FetchFromDatabase();
}
catch (SqlException ex) when (ex.Number == 547)
{
    // 仅当错误码为547(外键约束冲突)时捕获
    Console.WriteLine("数据库外键冲突,请检查关联数据");
}
catch (SqlException ex)
{
    // 其他数据库错误
    Console.WriteLine("数据库错误:" + ex.Message);
}

3.2 自定义异常类

对于业务逻辑错误,可以定义自己的异常类型,让代码更清晰:

// 自定义业务异常
public class BusinessException : Exception
{
    public BusinessException(string message) : base(message) { }
}

// 使用示例
try
{
    if (!ValidateInput(input))
    {
        throw new BusinessException("输入数据不合法");
    }
}
catch (BusinessException ex)
{
    Console.WriteLine("业务错误:" + ex.Message);
}

四、全局异常处理策略

4.1 ASP.NET Core 的全局异常处理

在 Web 应用中,可以使用中间件捕获所有未处理的异常:

// 技术栈:ASP.NET Core
public class ExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public ExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            // 记录日志
            LogError(ex);
            
            // 返回友好错误信息
            context.Response.StatusCode = 500;
            await context.Response.WriteAsync("服务器内部错误");
        }
    }
}

然后在 Startup.cs 中注册:

app.UseMiddleware<ExceptionMiddleware>();

4.2 控制台应用的全局异常处理

对于控制台程序,可以订阅 AppDomain 的未处理异常事件:

// 技术栈:C# (.NET Core)
static void Main()
{
    AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
    {
        var ex = (Exception)e.ExceptionObject;
        Console.WriteLine("未处理异常:" + ex.Message);
        // 记录日志或执行其他清理操作
    };

    // 主程序逻辑
    RunApplication();
}

五、异常处理的最佳实践

  1. 不要忽略异常:空的 catch 块是万恶之源,至少应该记录日志。
  2. 避免过度捕获:不要用 catch (Exception) 包裹所有代码,应该只捕获你能处理的异常。
  3. 提供有意义的错误信息:异常消息应该帮助开发者快速定位问题。
  4. 考虑性能影响:异常处理是有开销的,避免在循环中频繁抛出异常。

六、总结

良好的异常处理策略能显著提升程序的稳定性。C# 提供了灵活的工具,但关键还是在于开发者如何合理运用。无论是简单的 try-catch,还是全局异常处理,目标都是让程序在出错时能优雅降级,而不是直接崩溃。