404 Not Found

404 Not Found


nginx

异常处理

try/catch/finally 基本机制

C# 使用 trycatchfinally 三个关键字构建异常处理机制。try 块中放置可能抛出异常的代码,catch 块捕获并处理异常,finally 块无论是否发生异常都会执行,常用于资源释放。

CSHARP
try
{
    int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"捕获异常:{ex.Message}");
}
finally
{
    Console.WriteLine("finally 块始终执行");
}
TEXT
捕获异常:Attempted to divide by zero.
finally 块始终执行

多个 catch 块

可以按顺序捕获不同类型的异常,范围小的异常类型应放在前面。

CSHARP
try
{
    int[] arr = { 1, 2, 3 };
    Console.WriteLine(arr[10]);
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"索引越界:{ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"通用异常:{ex.Message}");
}
TEXT
索引越界:Index was outside the bounds of the array.

常见异常类型

异常类型 触发场景
FormatException 字符串格式不正确,如 int.Parse("abc")
NullReferenceException 访问 null 对象的成员
IndexOutOfRangeException 数组索引超出范围
DivideByZeroException 整数除以零
OverflowException 算术溢出(checked 上下文中)
FileNotFoundException 文件不存在
ArgumentException 方法参数无效

示例

CSHARP
try
{
    int num = int.Parse("hello");
}
catch (FormatException)
{
    Console.WriteLine("格式异常:字符串无法转换为整数");
}

string s = null;
try
{
    Console.WriteLine(s.Length);
}
catch (NullReferenceException)
{
    Console.WriteLine("空引用异常:对象为 null");
}
▶ 试一试
TEXT
格式异常:字符串无法转换为整数
空引用异常:对象为 null

异常类层次结构

所有异常类的继承关系如下:

TEXT
Object
  └─ Exception
       └─ SystemException
            ├─ FormatException
            ├─ NullReferenceException
            ├─ IndexOutOfRangeException
            ├─ DivideByZeroException
            ├─ OverflowException
            ├─ FileNotFoundException
            └─ ArgumentException
                 └─ ArgumentNullException

Exception 类属性

Exception 类提供了以下常用属性用于获取异常的详细信息:

属性 说明
Message 描述异常的可读文本
StackTrace 异常发生时的调用堆栈信息
InnerException 导致当前异常的内部异常
Source 引发异常的应用程序或对象名称
TargetSite 抛出异常的方法

示例

CSHARP
try
{
    int zero = 0;
    int result = 100 / zero;
}
catch (Exception ex)
{
    Console.WriteLine($"Message: {ex.Message}");
    Console.WriteLine($"Source: {ex.Source}");
    Console.WriteLine($"TargetSite: {ex.TargetSite}");
}
▶ 试一试
TEXT
Message: Attempted to divide by zero.
Source: ConsoleApp
TargetSite: Void Main()

throw 抛出异常

使用 throw 关键字可以主动抛出异常。重新抛出时,throw; 保留原始堆栈信息,而 throw ex; 会重置堆栈,应避免使用后者。

示例

CSHARP
void CheckAge(int age)
{
    if (age < 0)
    {
        throw new ArgumentException("年龄不能为负数", nameof(age));
    }
    Console.WriteLine($"年龄:{age}");
}

try
{
    CheckAge(-5);
}
catch (ArgumentException ex)
{
    Console.WriteLine(ex.Message);
}
▶ 试一试
TEXT
年龄不能为负数 (Parameter 'age')

throw 与 throw ex 的区别

CSHARP
void InnerMethod()
{
    throw new InvalidOperationException("内部错误");
}

void OuterMethod()
{
    try
    {
        InnerMethod();
    }
    catch (Exception ex)
    {
        throw;
    }
}

try
{
    OuterMethod();
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
}
TEXT
   at InnerMethod() in Program.cs:line 2
   at OuterMethod() in Program.cs:line 10
💡 提示: 始终使用 throw; 重新抛出异常,它保留完整的调用堆栈,便于定位问题根源。throw ex; 会将堆栈截断到当前方法,丢失原始异常位置信息。

异常筛选器

C# 6 引入了异常筛选器 when 子句,允许在 catch 中添加条件,只有条件为 true 时才捕获该异常。

示例

CSHARP
try
{
    throw new HttpRequestException("网络超时,请稍后重试");
}
catch (HttpRequestException ex) when (ex.Message.Contains("超时"))
{
    Console.WriteLine("捕获超时异常,准备重试");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"其他网络异常:{ex.Message}");
}
▶ 试一试
TEXT
捕获超时异常,准备重试

自定义异常类

通过继承 Exception 创建自定义异常类,通常需要实现三个构造函数:无参构造函数、带消息参数的构造函数、带消息和内部异常的构造函数。

示例

CSHARP
class InsufficientBalanceException : Exception
{
    public decimal Balance { get; }
    public decimal Amount { get; }

    public InsufficientBalanceException()
        : base("余额不足") { }

    public InsufficientBalanceException(string message)
        : base(message) { }

    public InsufficientBalanceException(string message, Exception innerException)
        : base(message, innerException) { }

    public InsufficientBalanceException(decimal balance, decimal amount)
        : base($"余额不足:当前余额 {balance},需要 {amount}")
    {
        Balance = balance;
        Amount = amount;
    }
}

class BankAccount
{
    public decimal Balance { get; private set; }

    public BankAccount(decimal balance)
    {
        Balance = balance;
    }

    public void Withdraw(decimal amount)
    {
        if (amount > Balance)
        {
            throw new InsufficientBalanceException(Balance, amount);
        }
        Balance -= amount;
        Console.WriteLine($"取款成功,余额:{Balance}");
    }
}

var account = new BankAccount(100);
try
{
    account.Withdraw(200);
}
catch (InsufficientBalanceException ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine($"余额:{ex.Balance},尝试取款:{ex.Amount}");
}
▶ 试一试
TEXT
余额不足:当前余额 100,需要 200
余额:100,尝试取款:200

异常处理最佳实践

捕获具体异常,不要只捕获 Exception

CSHARP
try
{
    int value = int.Parse(input);
    Console.WriteLine(100 / value);
}
catch (FormatException)
{
    Console.WriteLine("输入不是有效整数");
}
catch (DivideByZeroException)
{
    Console.WriteLine("不能输入零");
}
⚠️ 注意: 不要用空的 catch 块吞掉异常,这会使问题难以排查。

不要吞掉异常

CSHARP
try
{
    File.WriteAllText("data.txt", content);
}
catch (Exception)
{
}

上面的代码隐藏了所有错误,是错误做法。至少应记录日志或向上抛出。

使用 finally 释放资源

CSHARP
FileStream fs = null;
try
{
    fs = new FileStream("data.txt", FileMode.Open);
    int b = fs.ReadByte();
}
finally
{
    fs?.Dispose();
}

使用 using 语句替代 try-finally

实现了 IDisposable 接口的资源应优先使用 using 语句,它会在作用域结束时自动调用 Dispose,即使发生异常也能保证资源释放。

CSHARP
using (var fs = new FileStream("data.txt", FileMode.Open))
{
    int b = fs.ReadByte();
}
📌 要点: using 语句等价于 try-finallyDispose() 调用,是资源管理的首选方式。

使用 InnerException 包装异常

CSHARP
try
{
    SaveToFile();
}
catch (IOException ioEx)
{
    throw new ApplicationException("保存数据失败", ioEx);
}

通过 InnerException 保留原始异常信息,不丢失上下文。

❓ 常见问题

Q throw;throw ex; 有什么区别?
A throw; 保留原始堆栈跟踪,throw ex; 将堆栈重置为当前方法,调试时应使用 throw;
Q finally 块什么时候不会执行?
A 当程序被强制终止(如 Environment.FailFast)或发生 StackOverflowException 时,finally 可能不会执行。
Q 可以只有 try-finally 没有 catch 吗?
A 可以。这种写法不处理异常,但保证资源清理,异常会继续向上传播。
Q 自定义异常类必须继承 Exception 吗?
A 是的,所有异常类都必须继承自 Exception(直接或间接),才能被 catch 捕获。
Q 异常筛选器 when 有什么优势?
A when 子句在不匹配时不会捕获异常,异常继续传播,比在 catch 内部判断更高效且语义更清晰。

📖 小节

📝 作业

  1. 编写一个方法 int SafeParse(string s),使用 try-catch 捕获 FormatException,解析失败时返回 0 并打印警告信息
  2. 编写一个自定义异常 InvalidGradeException,包含 Grade 属性,当成绩不在 0-100 范围时抛出
  3. 编写代码演示 throw;throw ex; 的区别,分别打印两者的 StackTrace 并观察差异
  4. 使用异常筛选器 when 实现:捕获 FileNotFoundException 时仅当文件路径以 "config" 开头时才处理,否则继续传播
  5. 编写一个文件读取方法,使用 using 语句确保 StreamReader 正确释放,并处理可能出现的 FileNotFoundExceptionIOException
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏