一、C#异常处理的基本概念

在C#开发中,异常处理是保证程序健壮性的重要手段。当程序运行时遇到意外情况(比如文件不存在、网络连接失败、空引用等),系统会抛出异常。如果不处理这些异常,程序可能会直接崩溃,给用户带来糟糕的体验。

C#默认的异常处理机制依赖于try-catch-finally结构,这是最基础也是最常用的方式。我们先来看一个简单的例子:

// 示例1:基本的try-catch-finally结构
try
{
    // 尝试读取一个可能不存在的文件
    string content = File.ReadAllText("nonexistent.txt");
    Console.WriteLine(content);
}
catch (FileNotFoundException ex)
{
    // 捕获文件未找到异常
    Console.WriteLine($"文件未找到:{ex.Message}");
}
catch (Exception ex)
{
    // 捕获其他所有异常
    Console.WriteLine($"发生未知错误:{ex.Message}");
}
finally
{
    // 无论是否发生异常,都会执行
    Console.WriteLine("清理资源...");
}

这个例子展示了如何捕获特定异常(FileNotFoundException)和通用异常(Exception)。finally块通常用于释放资源,比如关闭文件句柄或数据库连接。

二、常见的C#异常类型及处理方法

C#内置了许多异常类型,不同的异常需要不同的处理方式。以下是几种常见的异常及其应对策略:

1. NullReferenceException(空引用异常)

这是最常见的异常之一,通常发生在尝试访问null对象的成员时。

// 示例2:空引用异常处理
string name = null;
try
{
    // 尝试访问null对象的属性
    Console.WriteLine(name.Length);
}
catch (NullReferenceException ex)
{
    Console.WriteLine($"对象未初始化:{ex.Message}");
}

解决方法

  • 使用?.操作符进行安全访问(name?.Length)。
  • 在访问对象前进行判空(if (name != null))。

2. ArgumentException(参数异常)

当方法接收到无效参数时抛出。

// 示例3:参数异常处理
void ProcessUser(int age)
{
    if (age < 0)
        throw new ArgumentException("年龄不能为负数", nameof(age));
    Console.WriteLine($"用户年龄:{age}");
}

try
{
    ProcessUser(-1);
}
catch (ArgumentException ex)
{
    Console.WriteLine($"参数错误:{ex.Message}");
}

解决方法

  • 在方法内部对参数进行校验,并在无效时抛出ArgumentException
  • 调用方应捕获并处理该异常。

3. InvalidOperationException(无效操作异常)

当对象状态不允许执行某些操作时抛出。

// 示例4:无效操作异常处理
List<int> numbers = new List<int>();
try
{
    // 尝试在空集合上调用First()
    int first = numbers.First();
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"集合为空,无法获取第一个元素:{ex.Message}");
}

解决方法

  • 使用FirstOrDefault()代替First(),避免抛出异常。
  • 在执行操作前检查集合是否为空。

三、高级异常处理技巧

1. 自定义异常

有时内置异常类型无法满足需求,可以创建自定义异常。

// 示例5:自定义异常
public class InsufficientBalanceException : Exception
{
    public InsufficientBalanceException(string message) : base(message) { }
}

void Withdraw(decimal amount)
{
    decimal balance = 100;
    if (amount > balance)
        throw new InsufficientBalanceException("余额不足");
    balance -= amount;
}

try
{
    Withdraw(200);
}
catch (InsufficientBalanceException ex)
{
    Console.WriteLine($"取款失败:{ex.Message}");
}

优点

  • 提供更清晰的错误信息。
  • 便于调用方针对特定异常进行处理。

2. 异常过滤器(C# 6.0+)

异常过滤器允许在catch块执行前增加条件判断。

// 示例6:异常过滤器
try
{
    throw new Exception("测试异常");
}
catch (Exception ex) when (ex.Message.Contains("测试"))
{
    Console.WriteLine("捕获测试异常");
}
catch (Exception ex)
{
    Console.WriteLine("捕获其他异常");
}

优点

  • 可以根据异常的具体属性决定是否处理。
  • 避免重复的if-else逻辑。

四、异常处理的最佳实践

  1. 不要捕获所有异常
    尽量避免直接捕获Exception,而应捕获具体的异常类型。

  2. 记录异常信息
    使用日志框架(如SerilogNLog)记录异常堆栈信息,便于排查问题。

  3. 避免在循环中捕获异常
    异常处理是有性能开销的,应尽量减少不必要的异常捕获。

  4. 合理使用finally
    确保资源释放,避免内存泄漏。

  5. 考虑使用AggregateException
    在并行编程中,多个任务可能抛出多个异常,AggregateException可以统一处理。

五、总结

C#的异常处理机制非常灵活,合理使用可以大幅提升程序的稳定性。本文介绍了基本用法、常见异常类型、高级技巧以及最佳实践。在实际开发中,应根据具体场景选择合适的处理方式,避免过度依赖异常控制流程。