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

posted @ 2025-10-15 13:32  Charltsing  阅读(38)  评论(0)    收藏  举报