C# 错误处理:从 try/catch 到专业技术
在 C# 和 .NET 的工业级开发世界中,错误处理不仅仅是 try/catch 结构。它是一整套哲学,决定了代码的可靠性、可读性和容错性。在高级水平上,开发者不仅要捕获异常,还要合理地设计、过滤、记录异常并恢复应用程序状态。在本文中,我们将剖析 C# 错误处理的关键方面,帮助您编写更优质、更专业的代码。
1. 深入理解 try/catch/finally:资源与上下文管理
基本的 try-catch-finally 结构对每个人来说都很熟悉,但让我们从另一个角度来审视它。finally 块的主要目标是确保资源被释放,即使发生异常也是如此。在现代 C# 版本中,通常使用 using 来实现这一点,但在处理非托管资源或复杂的完成逻辑时,finally 仍然是不可或缺的。
public void ProcessFile(string path){ FileStream? fileStream = null; try { fileStream = File.OpenRead(path); // 文件处理逻辑 } catch (FileNotFoundException ex) { // 特定处理:文件未找到 Log.Error($"文件 {path} 未找到:{ex.Message}"); throw; // 重新抛出异常,保留调用堆栈 } catch (UnauthorizedAccessException) { Log.Warn($"无法访问文件:{path}"); // 可以执行替代操作,不重新抛出异常 return; } finally { // 确保资源释放 fileStream?.Dispose(); }}请注意使用 throw;(不带参数)——这是关键点。与 throw ex; 不同,throw; 结构保留了原始的调用堆栈,这对调试至关重要。在实际项目中切勿使用 throw ex;,因为它会销毁原始错误发生位置的信息。
2. 自定义异常:设计领域错误模型
在大型应用程序中,标准的 .NET 异常通常不够用。创建自己的异常类可以精确地传递错误上下文,并简化上层处理。好的自定义异常应该是可序列化的,包含有意义的消息,并携带额外信息(例如,实体标识符)。
[Serializable]public class OrderProcessingException : Exception{ public int OrderId { get; } public string FaultyStep { get; }
public OrderProcessingException(int orderId, string faultyStep, string message) : base(message) { OrderId = orderId; FaultyStep = faultyStep; }
public OrderProcessingException(int orderId, string faultyStep, string message, Exception inner) : base(message, inner) { OrderId = orderId; FaultyStep = faultyStep; }
// 用于反序列化的构造函数 protected OrderProcessingException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { OrderId = info.GetInt32(nameof(OrderId)); FaultyStep = info.GetString(nameof(FaultyStep)) ?? string.Empty; }
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(OrderId), OrderId); info.AddValue(nameof(FaultyStep), FaultyStep); }}
// 使用示例public void ValidateOrder(int orderId){ try {