异常处理与最佳实践
翻译整理自 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:suggestion14. 性能提示
- 列表预分配
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 与异常后,你已经具备构建企业级应用的能力。