C# 异常处理详解

一、什么是异常

异常(Exception):程序运行过程中出现的非预期错误,比如除0、数组越界、空引用、文件不存在等。
C# 中所有异常都派生自 System.Exception 基类,属于运行时错误,语法错误编译阶段就会拦截。

常见系统内置异常

异常类 说明
NullReferenceException 空引用异常(对象为null却调用成员)
IndexOutOfRangeException 数组/集合下标越界
DivideByZeroException 整数除以0
FileNotFoundException 文件未找到
ArgumentNullException 方法传入null参数

二、C# 异常处理核心关键字

C# 使用 trycatchfinallythrow 四大关键字处理异常。

1. try-catch 基础结构

作用:监控可能出错的代码,捕获异常并处理,避免程序直接崩溃。

using System;

class Program
{
    static void Main()
    {
        try
        {
            // 可能出现异常的代码
            int a = 10;
            int b = 0;
            int res = a / b; // 触发除0异常
            Console.WriteLine("执行正常");
        }
        catch (DivideByZeroException ex)
        {
            // 捕获并处理 指定类型异常
            Console.WriteLine($"捕获到除0异常:{ex.Message}");
        }

        Console.WriteLine("程序继续运行...");
    }
}

2. 多 catch 块(捕获多种异常)

多个catch从上到下匹配先写子类异常,后写父类异常,顺序不能颠倒。

try
{
    int[] arr = { 1, 2, 3 };
    arr[5] = 10; // 数组越界
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"数组越界:{ex.Message}");
}
catch (NullReferenceException ex)
{
    Console.WriteLine($"空引用:{ex.Message}");
}
catch (Exception ex)
{
    // 万能捕获:所有异常的父类,放最后
    Console.WriteLine($"未知异常:{ex.Message}");
}

3. try-catch-finally 结构

finally 特点无论是否发生异常,代码一定会执行
适用场景:释放资源(关闭文件、数据库连接、网络流)。

try
{
    Console.WriteLine("执行业务代码");
    int num = 1 / 0;
}
catch (Exception ex)
{
    Console.WriteLine($"异常:{ex.Message}");
}
finally
{
    // 必定执行,用于资源释放
    Console.WriteLine("finally 资源释放");
}

4. try-finally(无catch)

只监控代码、释放资源,不捕获异常,异常会继续向上抛出。

try
{
    // 可能出错代码
}
finally
{
    // 释放资源
}

三、throw 抛出异常

1. 主动抛出系统异常

手动触发异常,用于参数校验、逻辑拦截

static void CheckAge(int age)
{
    if (age < 0)
    {
        // 主动抛出参数异常
        throw new ArgumentOutOfRangeException(nameof(age), "年龄不能为负数");
    }
    Console.WriteLine($"年龄合法:{age}");
}

// 调用
try
{
    CheckAge(-5);
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine(ex.Message);
}

2. 异常向上传递(throw 单独使用)

catch中使用 throw;把当前异常继续向上抛出,交由上层调用者处理。

static void Test()
{
    try
    {
        int a = 1 / 0;
    }
    catch (Exception ex)
    {
        Console.WriteLine("内层捕获,继续上抛");
        throw; // 异常向上传递
    }
}

static void Main()
{
    try
    {
        Test();
    }
    catch (Exception ex)
    {
        Console.WriteLine("外层捕获:" + ex.Message);
    }
}

四、Exception 常用属性

异常对象ex常用成员,用于排查错误:

try
{
    int a = 1 / 0;
}
catch (Exception ex)
{
    Console.WriteLine($"异常描述:{ex.Message}");      // 错误简要信息
    Console.WriteLine($"异常来源:{ex.Source}");      // 出错程序集
    Console.WriteLine($"调用栈:{ex.StackTrace}");    // 报错代码位置(排错核心)
    Console.WriteLine($"内部异常:{ex.InnerException}"); // 嵌套异常
}

五、自定义异常(重点)

当系统内置异常无法满足业务需求时,自定义异常类,规范业务报错。

步骤

  1. 新建类,继承自 Exception
  2. 实现三个构造函数(C# 规范写法)

示例:自定义业务异常

// 自定义用户操作异常
public class UserOperationException : Exception
{
    // 无参构造
    public UserOperationException() { }

    // 带错误信息构造
    public UserOperationException(string message) : base(message) { }

    // 错误信息 + 内部异常(异常嵌套)
    public UserOperationException(string message, Exception innerEx) 
        : base(message, innerEx) { }
}

// 使用自定义异常
class Program
{
    static void Login(string userName)
    {
        if (string.IsNullOrEmpty(userName))
        {
            // 抛出自定义异常
            throw new UserOperationException("用户名不能为空!");
        }
        Console.WriteLine("登录成功");
    }

    static void Main()
    {
        try
        {
            Login("");
        }
        catch (UserOperationException ex)
        {
            Console.WriteLine($"业务异常:{ex.Message}");
        }
    }
}

六、异常处理最佳实践(面试/开发常用)

  1. 不要滥用 try-catch
    正常逻辑不要加捕获,只处理可预见的异常
  2. 精准捕获异常
    优先捕获具体异常,少直接用 catch(Exception) 一网打尽。
  3. 资源释放写在 finally
    文件、数据库、网络连接必须在finally关闭。
  4. 不要空 catch
    catch{} 隐藏错误,难以排错,禁止使用。
  5. 异常分层处理
    底层抛出异常,上层统一捕获、日志记录、友好提示。
  6. 自定义异常用于业务场景
    区分系统错误和业务错误,便于维护。

七、补充:checked / unchecked 溢出检查

针对数值溢出异常:

  • checked:开启溢出检查,溢出直接抛 OverflowException
  • unchecked:关闭溢出检查(默认)
// 开启溢出检测
checked
{
    int max = int.MaxValue;
    max++; // 抛出溢出异常
}

笔记总结

  1. 四大关键字:try(监控)、catch(捕获)、finally(释放资源)、throw(抛异常)
  2. 异常继承链:所有异常 → Exception
  3. catch遵循先子类、后父类
  4. finally 必执行,专用于资源回收
  5. 业务场景推荐自定义异常,代码更规范
  6. 开发原则:精准捕获、不吞异常、合理抛异常

更多推荐