1.何时需要自定义异常?
在以下情况下,应该创建自定义异常:
1.业务逻辑错误
标准异常无法准确描述业务错误。需要特定的错误信息和处理逻辑。示例:账户余额不足、订单状态无效等。
2.需要额外的错误信息
标准异常无法提供足够的上下文信息。需要添加自定义属性来存储额外信息。示例:验证失败时需要显示哪些字段有问题。
3.需要特定的异常处理
调用者需要根据异常类型采取不同的处理措施。标准异常类型不够具体。示例:网络错误vs业务错误需要不同的处理。
4.API设计
公开的API需要明确的异常类型。让调用者能够清楚地知道可能发生的错误。示例:库或框架提供的异常类型。
2.创建自定义异常类
自定义异常类必须继承自Exception或其子类。
基本结构
C# public classCustomException : Exception { public CustomException() :base() { } public CustomException(string message): base(message) { } public CustomException(string message, Exception innerException):base(message, innerException) { } } |
C# /// <summary> ///表示余额不足时抛出的异常。 /// 包含当前余额和所需金额信息,便于调用方处理。 /// </summary> public class InsufficientBalanceException : Exception { /// <summary> /// 获取当前账户余额。 /// </summary> public decimal CurrentBalance { get; }
/// <summary> /// 获取操作所需的金额。 /// </summary> public decimal RequiredAmount { get; }
/// <summary> /// 初始化异常实例,使用当前余额和所需金额构造错误消息。 /// </summary> /// <param name="currentBalance">当前余额</param> /// <param name="requiredAmount">所需金额</param> public InsufficientBalanceException(decimal currentBalance, decimal requiredAmount) : base($"余额不足。当前余额:{currentBalance},需要:{requiredAmount}") { CurrentBalance = currentBalance; RequiredAmount = requiredAmount; }
/// <summary> /// 初始化异常实例,并指定内部异常。 /// </summary> /// <param name="currentBalance">当前余额</param> /// <param name="requiredAmount">所需金额</param> /// <param name="innerException">导致当前异常的原始异常</param> public InsufficientBalanceException(decimal currentBalance, decimal requiredAmount, Exception innerException) : base($"余额不足。当前余额:{currentBalance},需要:{requiredAmount}", innerException) { CurrentBalance = currentBalance; RequiredAmount = requiredAmount; } } |
使用示例
C# void Withdraw(decimal amount) { if (balance < amount){ throw new InsufficientBalanceException(balance, amount); } balance-= amount; } |
3.异常命名规范
自定义异常类应该遵循命名规范:
命名规则:类名以Exception结尾。使用描述性的名称,清楚地表达异常的含义。使用 PascalCase 命名。
说明:命名应该清楚地表达异常的含义,让调用者能够理解发生了什么错误。
4.异常设计原则
1.可序列化
如果异常需要在不同进程或机器间传递,应该标记为可序列化。
使用[Serializable]特性。
2.提供有意义的错误消息
错误消息应该清楚地说明发生了什么错误。包含足够的上下文信息。
3.添加自定义属性
如果需要额外的错误信息,添加自定义属性。属性应该是只读的,避免修改。
4.提供多个构造函数
提供无参构造函数。提供带消息的构造函数。提供带消息和内部异常的构造函数。
5.实现序列化支
如果标记为可序列化,需要实现序列化构造函数。实现GetobjectData方法。
5.完整示例:可序列化异常
C# using System; using System.Runtime.Serialization;
[Serializable] public class ValidationException : Exception { /// <summary> ///获取无效的字段名称数组。 /// </summary> public string[] InvalidFields { get; }
/// <summary> /// 初始化异常实例,指定无效字段列表。 /// </summary> /// <param name="invalidFields">无效字段名称数组</param> public ValidationException(string[] invalidFields) : base($"验证失败。无效字段:{string.Join(",", invalidFields)}") { InvalidFields = invalidFields; }
/// <summary> /// 初始化异常实例,指定无效字段列表和内部异常。 /// <summary> /// <param name="invalidFields">无效字段名称数组</param> /// <param name="innerException">内部异常</param> public ValidationException(string[] invalidFields, Exception innerException) : base($"验证失败。无效字段:{string.Join(",", invalidFields)}", innerException) { InvalidFields = invalidFields; }
/// <summary> /// 序列化构造函数(用于反序列化)。 /// </summary> /// <param name="info">序列化信息</param> /// <param name="context">流上下文</param> protected ValidationException(SerializationInfo info, StreamingContext context) : base(info, context) { InvalidFields = (string[])info.GetValue(nameof(InvalidFields), typeof(string[])); }
/// <summary> /// 自定义序列化数据,将 InvalidFields 添加到序列化信息中。 /// </summary> /// <param name="info">序列化信息</param> /// <param name="context">流上下文</param> public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(InvalidFields), InvalidFields); } } |
6.异常使用建议
何时使用自定义异常?
业务逻辑错误,标准异常无法准确描述。
需要额外的错误信息。
需要特定的异常处理逻辑。
公开的API需要明确的异常类型。
何时使用标准异常?
参数验证错误:使用 ArgumentException、ArgumentNullException。
对象状态错误:使用InvalidOperationException。
空引用错误:使用NullReferenceException(通常由运行时抛出)。
索引越界:使用IndexOutOfRangeException(通常由运行时抛出)。
选择建议
优先使用标准异常,除非有特殊需求。
自定义异常应该表示特定的业务错误。
避免创建过多的自定义异常类型。
7.异常设计最佳实践
1.保持简单
异常类应该简单,只包含必要的属性和方法。避免在异常类中添加复杂的业务逻辑。
2.提供足够的上下文
错误消息应该包含足够的上下文信息。添加自定义属性存储额外的错误信息。
3.文档化异常
使用XML注释文档化异常。说明异常的使用场景和触发条件。
4.避免过度使用
不要为每个错误都创建自定义异常。优先使用标准异常,除非有特殊需求。
5.考虑异常层次结构
如果多个异常有共同特征,可以创建基类异常。使用继承来组织异常类型。