一、为什么需要关注异常处理
在开发过程中,程序难免会遇到各种意外情况,比如文件读取失败、网络连接超时、数据库查询异常等。如果这些异常没有被妥善处理,轻则导致功能失效,重则可能让整个程序崩溃。C# 提供了强大的异常处理机制,但很多开发者往往只是简单用 try-catch 包裹一下,甚至直接忽略异常,这显然是不够的。
举个例子,假设我们有一个读取配置文件的方法:
// 技术栈:C# (.NET Core)
public string ReadConfigFile(string filePath)
{
// 直接读取文件,没有任何异常处理
return File.ReadAllText(filePath);
}
如果 filePath 不存在,或者程序没有权限访问该文件,就会抛出 IOException 或 UnauthorizedAccessException,导致程序中断。显然,这样的代码是不健壮的。
二、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();
}
五、异常处理的最佳实践
- 不要忽略异常:空的
catch块是万恶之源,至少应该记录日志。 - 避免过度捕获:不要用
catch (Exception)包裹所有代码,应该只捕获你能处理的异常。 - 提供有意义的错误信息:异常消息应该帮助开发者快速定位问题。
- 考虑性能影响:异常处理是有开销的,避免在循环中频繁抛出异常。
六、总结
良好的异常处理策略能显著提升程序的稳定性。C# 提供了灵活的工具,但关键还是在于开发者如何合理运用。无论是简单的 try-catch,还是全局异常处理,目标都是让程序在出错时能优雅降级,而不是直接崩溃。
评论