C# 12与.NET 8实战指南:20个提升代码质量的最佳实践
1. 使用 required + init 实现更安全的对象创建
确保某些属性在对象创建时必须被设置。通过避免部分初始化的对象来减少错误。
public class User
{
public required string Name { get; init; }
public required string Email { get; init; }
}
var user = new User { Name = "Alex", Email = "alex@email.com" };
✅ 强制开发者在对象创建时设置重要属性。不再出现"糟糕,忘记设置邮箱"的bug。
2. 启用可空引用类型。立刻行动。
让编译器对可能的空引用问题发出警告,在运行前发现问题。
#nullable enable
string? name = GetUserInput();
Console.WriteLine(name.Length); // 编译器警告!
✅ 在编译时捕获潜在的空值问题,而不是在生产环境中救火。
3. 使用文件作用域类型实现真正封装
保持工具类在文件内私有,避免污染全局命名空间,帮助强制执行边界。
file class StringHelper
{
public static bool IsNullOrWhitespace(string? value) => string.IsNullOrWhiteSpace(value);
}
✅ 保持辅助方法在文件内私有。由编译器强制执行的清晰架构。
4. 使用集合表达式获得更简洁语法
用更简洁的语法创建数组和列表。无需使用new[]或new List<>()。
int[] numbers = [1, 2, 3];
List<string> names = ["Alice", "Bob"];
✅ 更少干扰,更多专注。数组和列表的使用感觉更自然。
5. 使用record处理不可变数据
Record是DTO和只读数据的理想选择。你获得内置的相等性、不可变性和解构功能。
public record Product(string Id, string Name);
✅ 不可变性 = 安全性。完美适用于DTO和值对象。
6. 除非在UI代码中,否则避免使用async void
async void会吞掉异常。优先使用Task或ValueTask,以便调用者可以await并正确处理错误。
public async Task SaveAsync() { } // ✅
public async void SaveAsync() { } // ❌
✅ Task让你能够捕获异常、测试方法并正确await。
7. 在性能关键路径中使用ValueTask
在性能关键代码中节省内存。与Task不同,如果结果已准备就绪,ValueTask避免在堆上分配。
public ValueTask<string> GetTokenAsync() => new("cached_token");
✅ 减少分配。非常适合API或频繁调用的异步方法。
8. 不要在属性getter中放置逻辑
保持getter快速且无副作用。不要进行昂贵的数据库调用或可能令人意外的逻辑。
public bool IsValid => _status == "Active"; // ✅
public bool IsValid => ExpensiveDbCall(); // ❌
✅ 属性应该廉价且无副作用。
9. 使用[CallerArgumentExpression]改进验证
通过捕获传递给方法的实际表达式来改进验证和日志记录,使错误消息更清晰。
void Ensure(bool condition, [CallerArgumentExpression("condition")] string? msg = null)
{
if (!condition) throw new ArgumentException($"Check failed: {msg}");
}
✅ 在工具和验证库中超级方便。使日志更清晰。
10. 使用nameof()替代魔法字符串
用编译器检查的名称替换魔法字符串。重构安全且避免拼写错误。
logger.LogError(nameof(User.Email)); // ✅
✅ 安全重构。无拼写错误。不留遗憾。
11. 在日志记录中使用插值字符串处理程序
现代日志记录在日志级别关闭时跳过字符串创建,使其更快且内存高效。
logger.LogInformation($"Processing user {userId} at {DateTime.UtcNow}");
✅ 现代日志记录器优化掉未使用的日志 = 关闭时零分配。
12. 在私有对象上锁定——而不是this
锁应该是内部和私有的,以防止死锁和外部干扰。
private readonly object _lock = new();
lock (_lock) { /* safe work */ }
✅ 防止外部代码锁定你的对象而导致死锁。
13. 保持方法专注且小巧
一个方法 = 一个职责。较小的方法更易于阅读、测试和重用。
✅ 如果你的方法需要滚动查看,说明它做了太多事情。分解它。更易于测试和调试。
14. 使用dotnet format和分析器
dotnet format
✅ 强制执行干净、一致的代码。在PR审查中节省时间。
15. 对简单应用使用顶级语句
在小型控制台工具中无需样板Main()方法。非常适合快速脚本和学习演示。
Console.WriteLine("Hello, clean world!");
✅ 最少的样板代码 = 更快的原型开发。
16. 使用Span和ReadOnlySpan进行切片
这些类型让你可以在不分配内存的情况下处理内存切片。非常适合解析器和性能关键代码。
ReadOnlySpan<char> span = "1234567890".AsSpan(2, 4);
✅ 零分配。非常适合解析器、分词器和字符串操作。
17. 除非真正需要,否则避免使用dynamic
Dynamic禁用编译时检查和智能感知。优先使用强类型以尽早捕获错误。
dynamic data = GetLegacyData(); // ❌
✅ 你会失去编译时安全性和IntelliSense。优先使用强类型。
18. 对小逻辑使用表达式主体成员
对短方法或属性使用简洁语法。更清晰且更易读。
public int Double(int x) => x * 2;
✅ 更短、更清晰、更易读。
19. 使用默认接口实现(谨慎使用)
通过允许包含默认行为为接口增加灵活性,但不要在此隐藏复杂性。
public interface IWorker
{
void Work() => Console.WriteLine("Working...");
}
✅ 允许演进API而不会破坏契约——但不要滥用它。
20. 不需要可变性时优先使用readonly struct
防止意外更改并提高性能。在处理坐标等值类型时最佳。
public readonly struct Point(int X, int Y);
✅ 防止意外突变。也有助于提高性能。
总结
✔ required + init
✔ 可空引用类型 = 开启
✔ 避免async void
✔ 文件作用域辅助类
✔ 性能场景使用ValueTask
✔ 使用nameof()
✔ 使用Span
✔ 日志记录 = 插值字符串
✔ 避免dynamic
✔ 保持方法小巧
✔ 使用分析器 + dotnet format
✔ 永远不要在this上锁定
✔ 表达式主体方法
✔ 使用[CallerArgumentExpression]
✔ 小型工具使用顶级语句
✔ DTO使用record而非class

浙公网安备 33010602011771号