异常处理(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("数字超出范围");
}
十六、异常处理最佳实践
- 只捕获能够处理的异常 ,不要为了"防止报错"而滥用
catch (Exception)。 - 避免空的
catch块,至少记录日志或输出必要信息。 - 优先使用具体异常类型 (如
FileNotFoundException),最后再捕获Exception。 - 使用
using或using var管理实现了IDisposable的资源,不要依赖手动释放。 - 需要重新抛出异常时使用
throw;,保留完整的调用堆栈。 - 不要把异常当作正常业务流程(例如用异常判断用户输入是否合法),异常应只用于真正的异常情况。
- 在公共 API 中提供清晰、有意义的异常信息,便于调用方定位问题。
- 记录详细日志(异常类型、消息、堆栈、上下文),方便排查线上问题。
十七、知识总结
异常处理
│
├── try
├── catch
│ ├── Exception
│ ├── 多个catch
│ └── when过滤
├── finally
├── using
├── throw
├── 自定义异常
├── throw 与 throw ex
├── 常见异常类型
└── 最佳实践
学习建议
掌握异常处理后,建议继续学习以下 C# 核心内容:
- 委托(Delegate)
- 事件(Event)
- Lambda 表达式
- LINQ 查询
- 泛型(Generic)进阶
- 异步编程(async / await)
- 文件与流(IO)
- 反射(Reflection)
- 特性(Attribute)
- 依赖注入(Dependency Injection)
这些知识与异常处理结合使用,是开发 WinForms、WPF、ASP.NET Core 和企业级应用的基础。