Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 第二部分:C#进阶

第二部分:C#进阶

第4章:异常处理与调试

4.2 自定义异常

1. 为什么需要自定义异常?

在C#中,虽然.NET框架提供了丰富的内置异常类型(如ArgumentException、NullReferenceException等),但在实际开发中,我们经常需要根据业务逻辑定义特定的异常类型。自定义异常的优势包括:

  • 业务语义清晰:通过异常名称直接表达业务问题(如InsufficientFundsException)
  • 携带上下文信息:可添加自定义属性(如订单ID、账户余额等)
  • 统一处理逻辑:针对特定异常类型实现集中处理

2. 创建自定义异常的基本步骤

// 示例:银行转账业务的自定义异常
[Serializable]
public class InsufficientFundsException : Exception
{
    public decimal CurrentBalance { get; }
    public decimal RequiredAmount { get; }

    // 基本构造函数
    public InsufficientFundsException() { }

    // 带消息的构造函数
    public InsufficientFundsException(string message) 
        : base(message) { }

    // 带消息和内层异常的构造函数
    public InsufficientFundsException(string message, Exception inner) 
        : base(message, inner) { }

    // 带业务数据的构造函数
    public InsufficientFundsException(decimal currentBalance, decimal requiredAmount)
        : base($"Insufficient funds. Current: {currentBalance}, Required: {requiredAmount}")
    {
        CurrentBalance = currentBalance;
        RequiredAmount = requiredAmount;
    }

    // 序列化支持(可选)
    protected InsufficientFundsException(
        SerializationInfo info,
        StreamingContext context) : base(info, context) 
    {
        CurrentBalance = info.GetDecimal(nameof(CurrentBalance));
        RequiredAmount = info.GetDecimal(nameof(RequiredAmount));
    }

    public override void GetObjectData(
        SerializationInfo info, 
        StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(CurrentBalance), CurrentBalance);
        info.AddValue(nameof(RequiredAmount), RequiredAmount);
    }
}

3. 自定义异常的最佳实践

  1. 命名规范:

    • 类名以"Exception"结尾
    • 使用PascalCase命名法
    • 名称应明确描述异常场景
  2. 继承选择:

    • 通常继承自Exception基类
    • 对于参数验证问题可继承ArgumentException
    • 对于无效操作可继承InvalidOperationException
  3. 序列化支持:

    • 添加[Serializable]特性
    • 实现序列化构造函数
    • 重写GetObjectData方法
  4. 异常信息:

    • 提供有意义的错误消息
    • 包含相关业务数据
    • 避免暴露敏感信息

4. 使用自定义异常示例

public class BankAccount
{
    private decimal _balance;

    public void Withdraw(decimal amount)
    {
        if (amount > _balance)
        {
            throw new InsufficientFundsException(_balance, amount);
        }
        _balance -= amount;
    }
}

// 调用示例
try
{
    var account = new BankAccount { Balance = 100 };
    account.Withdraw(200);
}
catch (InsufficientFundsException ex)
{
    Console.WriteLine($"操作失败:{ex.Message}");
    Console.WriteLine($"当前余额:{ex.CurrentBalance}");
    Console.WriteLine($"尝试提取:{ex.RequiredAmount}");
    // 记录日志或执行补偿逻辑
}

5. 高级主题

  1. 异常过滤器(C# 6.0+):

    try { ... }
    catch (InsufficientFundsException ex) when (ex.CurrentBalance > 0)
    {
        // 仅当余额大于0时捕获
    }
    
  2. 异常包装模式:

    try { ... }
    catch (DbUpdateException ex)
    {
        throw new DataAccessException("数据库更新失败", ex);
    }
    
  3. 全局异常处理(ASP.NET Core示例):

    app.UseExceptionHandler(errorApp =>
    {
        errorApp.Run(async context =>
        {
            var exceptionHandler = context.Features.Get<IExceptionHandlerFeature>();
            if (exceptionHandler?.Error is BusinessException bizEx)
            {
                // 处理业务异常
            }
        });
    });
    

6. 常见问题解答

Q:何时应该创建自定义异常? A:当以下情况时考虑创建:

  • 需要表达特定的业务规则违反
  • 内置异常无法准确描述问题
  • 需要携带额外的上下文信息

Q:自定义异常应该包含多少逻辑? A:异常类应保持简单,主要职责是携带错误信息。避免在异常类中包含复杂的业务逻辑。

Q:如何测试自定义异常? A:使用单元测试验证:

  • 异常是否能被正确抛出
  • 包含的消息是否正确
  • 自定义属性是否被正确设置
  • 序列化/反序列化是否正常工作
Last Updated:: 5/3/25, 11:01 PM