异常处理与最佳实践

翻译整理自 Exceptions 与 C# Coding Conventions。

1. try / catch / finally

try
{
    int n = int.Parse("x");
}
catch (FormatException ex)
{
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;                              // 重新抛出
}
finally
{
    // 必执行
}

2. 异常过滤

catch (Exception ex) when (ex is not NullReferenceException)
{
    Console.WriteLine(ex);
}

catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
    // 仅处理 404
}

3. throw 重新抛出

try { DoWork(); }
catch (Exception ex)
{
    log.Error(ex);
    throw;                  // 保持原堆栈

    // ❌ throw ex;        // 重置堆栈,丢失上下文
}

// 包装异常
catch (Exception ex)
{
    throw new MyException("processing failed", ex);
}

4. 自定义异常

[Serializable]
public class BusinessException : Exception
{
    public int Code { get; }
    public BusinessException(int code, string message)
        : base(message) { Code = code; }
    public BusinessException(int code, string message, Exception inner)
        : base(message, inner) { Code = code; }
}

throw new BusinessException(404, "not found");

5. 常见异常类型

异常 触发场景
NullReferenceException 访问 null 引用
ArgumentNullException 参数为 null
ArgumentOutOfRangeException 参数超出范围
IndexOutOfRangeException 数组越界
InvalidOperationException 当前状态不允许
KeyNotFoundException 字典 key 缺失
NotImplementedException 未实现
NotSupportedException 不支持
TimeoutException 超时
TaskCanceledException 任务被取消
FileNotFoundException 文件未找到
FormatException 解析失败
OverflowException 数值溢出

6. 异常使用原则

  • 不要用异常做正常控制流
  • 抛出有意义的异常类型,而非基类
  • 异常信息包含足够上下文
  • 库不应吞异常,应包装或重新抛出
  • 公共 API 用 ArgumentNullException 等 ArgumentException 系列
  • 失败概率高的检查应使用 TryXxx 模式
public bool TryParse(string s, out int n) { ... }

if (!int.TryParse(s, out var n)) { /* fallback */ }

7. using 与资源释放

// using 语句(传统)
using (var fs = File.OpenRead("a.txt"))
{
    // ...
}

// using 声明(C# 8+)
using var fs = File.OpenRead("a.txt");
// 块结束自动 Dispose

// await using 异步
await using var fs = File.OpenRead("a.txt");

8. 垃圾回收与 Dispose

public class FileHolder : IDisposable
{
    private FileStream _fs;
    public FileHolder(string p) { _fs = File.OpenRead(p); }

    private bool _disposed;
    public void Dispose()
    {
        if (_disposed) return;
        _fs?.Dispose();
        _disposed = true;
        GC.SuppressFinalize(this);
    }
    ~FileHolder() => Dispose();
}

9. nullable 引用类型

#nullable enable

public class Service
{
    public string Name { get; set; }       // 不可空
    public string? Comment { get; set; }  // 可空
}

启用后编译器对 null 解引用做警告,配合 !(dammit operator):

string s = maybeNull!;     // 显式告诉编译器"我知道不是 null"

10. 命名约定

  • 类、方法、属性:PascalCase
  • 局部变量、参数:camelCase
  • 私有字段:_camelCase
  • 接口:I 前缀(IEnumerable)
  • 异常:Exception 后缀
  • 命名空间:Company.Product.Layer

11. 项目结构建议

MyApp/
├── src/
│   ├── MyApp.Domain/        # 领域层
│   ├── MyApp.Application/  # 应用层
│   ├── MyApp.Infrastructure/ # 基础设施
│   └── MyApp.Api/           # 入口(ASP.NET Core)
└── tests/
    └── MyApp.Tests/

12. 单元测试

using Xunit;

public class CalculatorTests
{
    [Theory]
    [InlineData(1, 2, 3)]
    [InlineData(10, 20, 30)]
    public void Add_Works(int a, int b, int expected)
    {
        var calc = new Calculator();
        Assert.Equal(expected, calc.Add(a, b));
    }
}
dotnet test
dotnet test --filter "FullyQualifiedName~Add_Works"

13. 静态分析

  • dotnet format:格式化
  • dotnet format analyzers:分析器
  • Roslyn 分析器:Microsoft.CodeAnalysis.FxCopAnalyzers
  • StyleCop:StyleCop.Analyzers
  • SonarAnalyzer

.editorconfig:

[*.cs]
dotnet_diagnostic.CA2007.severity = none
csharp_style_var_for_built_in_types = true:suggestion

14. 性能提示

  • 列表预分配 new List<int>(capacity)
  • 字符串拼接用 StringBuilder 或插值字符串
  • 大对象用 Span<T> / Memory<T>
  • 异步用 ValueTask 替代 Task
  • 频繁分配用 ArrayPool<T>.Shared
  • LINQ 在热路径可手写循环

15. 常见陷阱

  • 不实现 Equals/GetHashCode:字典 key 行为异常
  • 结构性变化与引用变化:record vs class
  • 闭包捕获 for 变量:C# 5+ 已修复 foreach,但 for 仍需 int copy = i;
  • async void:异常无法捕获,仅事件处理可用
  • deadlock:UI 中 Task.Wait() 會 deadlock
  • 装箱:List<object> 装 int 会频繁分配
  • 闭包分配:lambda 捕获会创建闭包对象
  • 结构性相等:record 用 == 比较值,普通 class 比较引用

小结

C# 是一门现代、优雅、生态丰富的语言。掌握类型系统、OOP、泛型、LINQ、async、IO 与异常后,你已经具备构建企业级应用的能力。