C# 异常处理详解

异常处理(Exception Handling)是 C# 中非常重要的知识点,它能够让程序在发生错误时不会直接崩溃,而是通过捕获异常进行处理,提高程序的稳定性和用户体验。


一、什么是异常(Exception)

异常(Exception)是程序运行过程中发生的错误。

例如:

  • 除数为 0
  • 数组越界
  • 文件不存在
  • 数据库连接失败
  • 网络连接失败
  • 空对象调用成员

例如:

cs 复制代码
int a = 10;
int b = 0;

Console.WriteLine(a / b);

运行结果:

cs 复制代码
Unhandled Exception:
System.DivideByZeroException

程序直接终止。


二、为什么需要异常处理

如果没有异常处理:

复制代码
程序开始
    ↓
发生错误
    ↓
程序退出

加入异常处理:

复制代码
程序开始
    ↓
发生错误
    ↓
捕获异常
    ↓
记录日志
    ↓
提示用户
    ↓
继续运行

因此:

异常处理的目的不是消除错误,而是优雅地处理错误


三、try-catch

最基本的异常处理方式。

语法:

cs 复制代码
try
{
    // 可能发生异常的代码
}
catch
{
    // 处理异常
}

例如:

cs 复制代码
try
{
    int a = 10;
    int b = 0;

    Console.WriteLine(a / b);
}
catch
{
    Console.WriteLine("发生异常!");
}

输出:

复制代码
发生异常!

程序不会崩溃。


四、捕获异常对象

可以获取详细错误信息。

cs 复制代码
try
{
    int a = 10;
    int b = 0;

    Console.WriteLine(a / b);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

输出:

cs 复制代码
Attempted to divide by zero.

其中:

复制代码
ex

就是异常对象。


五、Exception对象常用属性

例如:

cs 复制代码
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
    Console.WriteLine(ex.Source);
}

常见属性:

属性 说明
Message 错误信息
StackTrace 调用堆栈
Source 异常来源
InnerException 内部异常
HelpLink 帮助链接
TargetSite 出错的方法

例如:

cs 复制代码
catch(Exception ex)
{
    Console.WriteLine("错误:" + ex.Message);
    Console.WriteLine("位置:" + ex.StackTrace);
}

六、多个catch

不同异常可以分别处理。

例如:

cs 复制代码
try
{
    int[] arr = {1,2,3};

    Console.WriteLine(arr[5]);
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("数组越界");
}
catch (Exception)
{
    Console.WriteLine("未知错误");
}

输出:

复制代码
数组越界

原则:

子类异常放前面,父类异常放最后。

错误写法:

cs 复制代码
catch(Exception)
{
}

catch(IndexOutOfRangeException)
{
}

因为:

复制代码
Exception

已经把所有异常都捕获了。


七、finally

finally 无论是否发生异常都会执行。

语法:

cs 复制代码
try
{
}
catch
{
}
finally
{
}

例如:

cs 复制代码
try
{
    Console.WriteLine("开始");

    int a = 10;
    int b = 0;

    Console.WriteLine(a / b);
}
catch
{
    Console.WriteLine("发生异常");
}
finally
{
    Console.WriteLine("结束");
}

输出:

复制代码
开始
发生异常
结束

八、finally的用途

主要用于释放资源。

例如:

cs 复制代码
FileStream fs = null;

try
{
    fs = new FileStream("test.txt", FileMode.Open);
}
catch
{
}
finally
{
    if (fs != null)
    {
        fs.Close();
    }
}

即使发生异常:

复制代码
文件也会关闭

九、using代替finally

现代 C# 推荐使用:

cs 复制代码
using (FileStream fs = new FileStream("test.txt", FileMode.Open))
{
    // 使用文件
}

等价于:

cs 复制代码
try
{
}
finally
{
    Dispose()
}

更推荐使用:

cs 复制代码
using var fs = new FileStream("test.txt", FileMode.Open);

// 使用文件

离开作用域后自动释放资源。


十、throw抛出异常

可以主动抛出异常。

cs 复制代码
throw new Exception("发生错误");

例如:

cs 复制代码
Console.Write("请输入年龄:");

int age = int.Parse(Console.ReadLine());

if (age < 0)
{
    throw new Exception("年龄不能小于0");
}

程序:

复制代码
年龄不能小于0

十一、自定义异常

建议继承 Exception。

cs 复制代码
public class AgeException : Exception
{
    public AgeException(string msg)
        : base(msg)
    {
    }
}

使用:

cs 复制代码
if(age<0)
{
    throw new AgeException("年龄错误");
}

捕获:

cs 复制代码
catch(AgeException ex)
{
    Console.WriteLine(ex.Message);
}

十二、throw 与 throw ex 的区别

很多初学者容易写成:

cs 复制代码
catch(Exception ex)
{
    throw ex;
}

实际上应该写:

cs 复制代码
catch(Exception ex)
{
    throw;
}

区别:

throw

保留原始异常位置。

复制代码
原始位置
↓

继续抛出

便于定位问题。


throw ex

重新创建调用栈。

复制代码
catch这里
↓

新的异常位置

原始调用位置会丢失。

推荐:

复制代码
throw;

十三、异常过滤(Exception Filter)

C# 支持使用 when 对异常进行条件过滤。

cs 复制代码
try
{
    // ...
}
catch (Exception ex) when (ex.Message.Contains("网络"))
{
    Console.WriteLine("网络异常");
}

这样可以在进入 catch 前根据条件决定是否处理异常。


十四、常见异常类型

异常 说明
Exception 所有异常基类
NullReferenceException 空引用
DivideByZeroException 除零
IndexOutOfRangeException 数组越界
FormatException 格式错误
InvalidCastException 类型转换失败
OverflowException 数值溢出
FileNotFoundException 文件不存在
IOException IO异常
UnauthorizedAccessException 权限不足
TimeoutException 超时
ArgumentException 参数错误
ArgumentNullException 参数为空
InvalidOperationException 非法操作

十五、实际开发案例

文件读取

cs 复制代码
try
{
    string text = File.ReadAllText("data.txt");
    Console.WriteLine(text);
}
catch (FileNotFoundException)
{
    Console.WriteLine("文件不存在");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("没有权限访问文件");
}
catch (IOException ex)
{
    Console.WriteLine($"IO错误:{ex.Message}");
}

数据转换

cs 复制代码
try
{
    Console.Write("请输入数字:");
    int number = int.Parse(Console.ReadLine());

    Console.WriteLine(number);
}
catch (FormatException)
{
    Console.WriteLine("请输入正确的数字");
}
catch (OverflowException)
{
    Console.WriteLine("数字超出范围");
}

十六、异常处理最佳实践

  1. 只捕获能够处理的异常 ,不要为了"防止报错"而滥用 catch (Exception)
  2. 避免空的 catch,至少记录日志或输出必要信息。
  3. 优先使用具体异常类型 (如 FileNotFoundException),最后再捕获 Exception
  4. 使用 usingusing var 管理实现了 IDisposable 的资源,不要依赖手动释放。
  5. 需要重新抛出异常时使用 throw;,保留完整的调用堆栈。
  6. 不要把异常当作正常业务流程(例如用异常判断用户输入是否合法),异常应只用于真正的异常情况。
  7. 在公共 API 中提供清晰、有意义的异常信息,便于调用方定位问题。
  8. 记录详细日志(异常类型、消息、堆栈、上下文),方便排查线上问题。

十七、知识总结

复制代码
异常处理
│
├── try
├── catch
│    ├── Exception
│    ├── 多个catch
│    └── when过滤
├── finally
├── using
├── throw
├── 自定义异常
├── throw 与 throw ex
├── 常见异常类型
└── 最佳实践

学习建议

掌握异常处理后,建议继续学习以下 C# 核心内容:

  1. 委托(Delegate)
  2. 事件(Event)
  3. Lambda 表达式
  4. LINQ 查询
  5. 泛型(Generic)进阶
  6. 异步编程(async / await)
  7. 文件与流(IO)
  8. 反射(Reflection)
  9. 特性(Attribute)
  10. 依赖注入(Dependency Injection)

这些知识与异常处理结合使用,是开发 WinForms、WPF、ASP.NET Core 和企业级应用的基础。

相关推荐
玖玥拾3 小时前
C# 语言进阶(十五)C# 游戏服务端 MySQL 数据库
服务器·开发语言·网络·数据库·mysql·c#
铅笔侠_小龙虾3 小时前
Rust 学习目录
开发语言·学习·rust
考虑考虑4 小时前
Sentinel安装
java·后端·微服务
IT_陈寒4 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端
云泽8084 小时前
从零吃透 C++ 异常:抛出捕获、栈展开、异常重抛与编码规范详解
开发语言·c++·代码规范
碎碎念_4924 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
灯澜忆梦4 小时前
GO---可见性规则
开发语言·golang
逝水无殇4 小时前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#
YIAN4 小时前
从手写 Agent 工具到 MCP 协议:一文搞懂大模型跨进程跨语言工具调用
后端·agent·mcp