C#异常处理 递归算法

C#异常处理完全指南

🔍 一、异常的核心概念

异常是什么?

异常 = 程序运行时的意外情况

就像开车时的突发事件(爆胎、没油、事故),需要特殊处理才能继续。

两种错误的对比:

错误类型 发生时间 特点 解决方法
编译错误 编译时(写代码时) 语法错误、类型错误 修改代码
运行错误 运行时(程序执行时) 数组越界、除零、空引用 异常处理

🎯 二、try-catch-finally 结构

基本结构(三段式)

csharp

复制代码
try
{
    // 尝试执行的代码(可能出错)
    // 这里是"危险区域"
}
catch (异常类型 变量名)
{
    // 捕获并处理异常
    // 这里是"急救站"
}
finally
{
    // 无论是否发生异常,都会执行
    // 这里是"清理现场"
    // 通常用于释放资源
}

📝 三、异常处理详细解析

1. try 块 - 尝试执行

csharp

复制代码
try
{
    // 把可能出错的代码放在这里
    string input = Console.ReadLine();
    int number = int.Parse(input);  // 可能抛出 FormatException
    int result = 100 / number;      // 可能抛出 DivideByZeroException
}

2. catch 块 - 捕获处理

csharp

复制代码
// 捕获特定异常
catch (FormatException ex)
{
    Console.WriteLine("输入的不是有效数字!");
    Console.WriteLine($"错误详情:{ex.Message}");
}

// 捕获多个异常(从具体到一般)
catch (DivideByZeroException)
{
    Console.WriteLine("不能除以零!");
}
catch (Exception ex)  // 通用异常(放在最后)
{
    Console.WriteLine($"发生未知错误:{ex.Message}");
    // 记录日志
    LogError(ex);
}

3. finally 块 - 清理资源

csharp

复制代码
FileStream file = null;
try
{
    file = File.OpenRead("data.txt");
    // 处理文件...
}
catch (FileNotFoundException)
{
    Console.WriteLine("文件不存在!");
}
finally
{
    // 无论是否出错,都要关闭文件
    if (file != null)
        file.Close();
    Console.WriteLine("资源已清理");
}

📊 四、常见异常类型速查表

异常类型 何时发生 示例
FormatException 格式转换失败 int.Parse("abc")
OverflowException 数值溢出 int.MaxValue + 1
DivideByZeroException 除以零 10 / 0
IndexOutOfRangeException 数组越界 arr[10](数组只有5个元素)
NullReferenceException 空引用 string s = null; s.Length;
FileNotFoundException 文件不存在 File.OpenRead("不存在的文件.txt")
IOException 输入输出错误 磁盘满、无权限
ArgumentException 参数无效 传给方法的参数不符合要求

🔧 五、异常处理最佳实践

1. 具体的异常先捕获

csharp

复制代码
try
{
    // ...
}
catch (FormatException ex)  // 具体的
{
    // 处理格式错误
}
catch (Exception ex)        // 一般的(放最后)
{
    // 处理其他所有异常
}

2. 不要"吞掉"异常

csharp

复制代码
// ❌ 错误:隐藏了错误
try
{
    ProcessData();
}
catch (Exception)
{
    // 什么都不做?用户不知道出错了!
}

// ✅ 正确:至少记录或通知
try
{
    ProcessData();
}
catch (Exception ex)
{
    LogError(ex);            // 记录日志
    ShowUserFriendlyMessage(); // 给用户友好提示
}

3. 使用 using 简化资源清理

csharp

复制代码
// 传统方式
FileStream file = null;
try
{
    file = File.OpenRead("data.txt");
    // 使用文件
}
finally
{
    file?.Close();
}

// 简化方式(推荐)
using (FileStream file = File.OpenRead("data.txt"))
{
    // 使用文件
    // 自动调用 file.Dispose(),相当于关闭
}

4. 抛出有意义的异常

csharp

复制代码
public void Withdraw(decimal amount)
{
    if (amount <= 0)
        throw new ArgumentException("取款金额必须大于0", nameof(amount));
    
    if (amount > Balance)
        throw new InvalidOperationException("余额不足");
    
    // 正常逻辑
    Balance -= amount;
}

⚠️ 六、避免的常见错误

1. 过度使用 try-catch

csharp

复制代码
// ❌ 错误:用异常处理正常逻辑
try
{
    if (array[index] != null)  // 正常检查
    {
        // ...
    }
}
catch (IndexOutOfRangeException)  // 不应该用异常!
{
    // ...
}

// ✅ 正确:用条件判断
if (index >= 0 && index < array.Length)
{
    if (array[index] != null)
    {
        // ...
    }
}

2. catch 块中再抛出不包装

csharp

复制代码
// ❌ 错误:丢失原始异常信息
try
{
    ProcessFile();
}
catch (Exception)
{
    throw;  // 直接重新抛出,可能丢失上下文
}

// ✅ 正确:包装异常,保留信息
try
{
    ProcessFile();
}
catch (Exception ex)
{
    throw new FileProcessingException("处理文件失败", ex);
}

3. 忽略 finally 的资源清理

csharp

复制代码
// ❌ 错误:忘记清理
SqlConnection conn = new SqlConnection(connectionString);
try
{
    conn.Open();
    // 使用数据库...
}
catch (Exception ex)
{
    // 处理异常,但连接没关闭!
}

// ✅ 正确:确保清理
SqlConnection conn = null;
try
{
    conn = new SqlConnection(connectionString);
    conn.Open();
    // ...
}
finally
{
    conn?.Close();  // 确保关闭
}

🛠️ 七、实用异常处理模式

模式1:重试机制

csharp

复制代码
int maxRetries = 3;
int retryCount = 0;

while (retryCount < maxRetries)
{
    try
    {
        ConnectToService();
        break;  // 成功则退出循环
    }
    catch (TimeoutException)
    {
        retryCount++;
        if (retryCount == maxRetries)
            throw;
        
        Console.WriteLine($"连接超时,第{retryCount}次重试...");
        Thread.Sleep(1000);  // 等待1秒后重试
    }
}

模式2:验证输入

csharp

复制代码
public int GetValidNumber()
{
    while (true)  // 直到输入有效
    {
        try
        {
            Console.Write("请输入一个数字:");
            string input = Console.ReadLine();
            return int.Parse(input);
        }
        catch (FormatException)
        {
            Console.WriteLine("输入的不是有效数字,请重新输入!");
        }
        catch (OverflowException)
        {
            Console.WriteLine("数字超出范围,请重新输入!");
        }
    }
}

模式3:资源安全访问

csharp

复制代码
public string ReadFileSafely(string filePath)
{
    try
    {
        return File.ReadAllText(filePath);
    }
    catch (FileNotFoundException)
    {
        return "文件不存在";
    }
    catch (UnauthorizedAccessException)
    {
        return "没有访问权限";
    }
    catch (IOException ex)
    {
        return $"读取文件失败:{ex.Message}";
    }
    catch (Exception ex)
    {
        LogError(ex);  // 记录未知错误
        return "系统错误";
    }
}

📈 八、异常处理层次结构

C#异常继承体系

text

复制代码
Object
└── Exception (所有异常的基类)
    ├── SystemException (系统异常)
    │   ├── ArgumentException (参数异常)
    │   ├── FormatException (格式异常)
    │   ├── ArithmeticException (算术异常)
    │   │   ├── DivideByZeroException (除零异常)
    │   │   └── OverflowException (溢出异常)
    │   ├── IndexOutOfRangeException (索引越界)
    │   └── NullReferenceException (空引用)
    └── ApplicationException (应用异常 - 自定义异常的基类)

自定义异常

csharp

复制代码
public class InsufficientFundsException : Exception
{
    public decimal CurrentBalance { get; }
    public decimal RequiredAmount { get; }
    
    public InsufficientFundsException(decimal current, decimal required)
        : base($"余额不足。当前余额:{current},需要:{required}")
    {
        CurrentBalance = current;
        RequiredAmount = required;
    }
}

// 使用
if (amount > Balance)
    throw new InsufficientFundsException(Balance, amount);

🎯 九、什么时候用异常处理?

应该用异常处理:

  • ✅ 外部依赖可能失败(文件、网络、数据库)

  • ✅ 用户输入不可控

  • ✅ 资源访问需要清理

  • ✅ 需要友好错误提示

不应该用异常处理:

  • ❌ 控制正常程序流程

  • ❌ 频繁发生的"正常"错误

  • ❌ 性能关键路径

  • ❌ 可以预防的错误(用条件检查)

💡 十、一句话总结

异常处理 = 给程序买保险

  • try:高风险操作

  • catch:出险理赔

  • finally:无论如何都要执行(像保险的必备条款)

  • 具体险种(异常类型)要明确,通用险(Exception)放最后


📋 十一、快速参考卡片

基本语法:

csharp

复制代码
try
{
    // 危险代码
}
catch (具体异常 ex)
{
    // 处理具体异常
}
catch (Exception ex)  // 通用,放最后
{
    // 处理其他所有
    // throw;  // 重新抛出
}
finally
{
    // 清理资源
}

常用方法:

csharp

复制代码
ex.Message        // 错误信息
ex.StackTrace     // 调用堆栈
ex.InnerException // 内部异常
ex.GetType()      // 异常类型

最佳实践:

  1. 具体异常先捕获

  2. 不要吞掉异常

  3. 使用using管理资源

  4. finally确保清理

  5. 抛出有意义的异常

记住:好的异常处理让程序更健壮,坏的异常处理让bug更隐蔽!

递归算法核心概念精要

🔄 递归本质定义

递归 = 自我调用 + 问题分解 + 终止条件

函数直接或间接调用自身,将复杂问题分解为相同结构的子问题,直到达到可直接求解的基准情形。

🎯 递归三要素(黄金法则)

1. 基准条件(Base Case)

递归必须终止的条件,防止无限循环。

csharp

复制代码
if (条件成立) return 结果;

2. 递归条件(Recursive Case)

问题如何分解为更小的相同问题。

csharp

复制代码
return 当前结果 + 函数(更小问题);

3. 向基准条件收敛

每次递归调用必须更接近基准条件。

🔄 递归执行过程

两个阶段:

  1. 递推阶段:不断深入调用,向基准条件推进

  2. 回归阶段:从基准条件开始,逐层返回结果

调用栈机制:

  • 每次递归调用压入调用栈

  • 返回时从栈顶弹出

  • 深度过大导致栈溢出

⚖️ 递归 vs 迭代

维度 递归 迭代(循环)
思维模型 自顶向下分解 自底向上构建
代码简洁性 更简洁直观 需要状态管理
性能开销 函数调用开销大 直接高效
内存使用 使用调用栈内存 通常更节省
适用问题 树状、分治、回溯 线性、序列处理

⚠️ 递归风险与限制

主要风险:

  1. 栈溢出:递归深度过大

  2. 重复计算:子问题重叠

  3. 性能低下:函数调用开销

  4. 难以调试:多层调用追踪困难

递归深度限制:

  • 默认约 1000-3000 层

  • 受系统栈大小限制

  • 深度问题优先考虑迭代

🎯 递归适用场景判断

适合递归的特征:

  1. 问题可分解为相同子问题

  2. 有明显的基准情形

  3. 子问题相互独立(或可优化)

  4. 问题规模呈树状/层次结构

不适合递归:

  1. 深度无法控制

  2. 性能要求极高

  3. 已有简洁迭代解法

  4. 问题规模线性增长

🔧 递归优化策略

1. 尾递归优化

递归调用是函数最后操作,某些编译器可优化为迭代。

2. 记忆化(Memoization)

缓存已计算结果,避免重复计算。

3. 迭代转换

深度过大时转换为迭代算法。

💡 递归思维模式

关键转变:

"如何解决" 转为:

  1. "基准情形是什么"

  2. "如何分解为更小的相同问题"

  3. "如何组合子问题的解"

思维验证:

  1. 是否所有路径都收敛到基准条件?

  2. 每次递归是否真正缩小问题规模?

  3. 是否存在重复计算?

  4. 深度是否可控?

📊 递归复杂度分析

时间复杂度公式:

text

复制代码
T(n) = 递归调用次数 × 每次调用的时间复杂度

常见复杂度:

  • 阶乘:O(n)

  • 朴素斐波那契:O(2ⁿ) ⚠️

  • 二分递归:O(log n)

  • 树遍历:O(n)

🎯 递归设计原则

必须遵守:

  1. 明确基准条件 - 无终止则无限

  2. 确保收敛性 - 每次更接近基准

  3. 避免副作用 - 纯函数更安全

  4. 控制递归深度 - 预防栈溢出

推荐实践:

  1. 先定义基准条件

  2. 再思考递归分解

  3. 验证收敛性

  4. 考虑性能优化

💎 一句话精髓

递归 = 用定义解决问题

将问题定义为其自身的简化版本,加上明确的终止条件,通过自我调用的链条将复杂问题逐步简化至可直接求解。


记住:递归是一种思维方式,而非单纯的编程技巧。掌握递归的核心在于理解"如何将大问题分解为小问题"和"何时停止分解"这两个关键点。

相关推荐
ejjdhdjdjdjdjjsl2 小时前
JSON序列化与反序列化实战指南
数据库·microsoft·c#
Macbethad10 小时前
区块链技术在分布式系统中的应用实践技术报告
开发语言·c#
玩泥巴的11 小时前
如何在.NET系统中快速集成飞书任务分配能力
c#·.net·二次开发·飞书
bugcome_com12 小时前
深入理解 C# 特性(Attribute):概念、实现与实战
c#·.net
WebRuntime12 小时前
所有64位WinForm应用都是Chromium浏览器(2)
javascript·c#·.net·web
Sunsets_Red14 小时前
待修改莫队与普通莫队优化
java·c++·python·学习·算法·数学建模·c#
时光追逐者14 小时前
一款基于 .NET 9 构建的企业级 Web RBAC 快速开发框架
前端·c#·.net·.net core
想你依然心痛14 小时前
【TextIn大模型加速器+火山引擎】打造智能文档处理流水线:从跨国药企手册到金融单据核验的全链路实战
金融·c#·火山引擎
kingwebo'sZone14 小时前
win11智能应用控制已阻止此应用
c#