.NET开发中的5个关键小习惯:从细节处提升代码质量

作者:微信公众号:【架构师老卢】
7-29 8:18
13

无论你已经编写.NET代码十年,还是刚刚发布第一个生产环境API,往往是那些小习惯带来了最大的改变。干净、高性能的代码很少来自大刀阔斧的重写,而是源于开发者每天做出的无数微小决策。

本博客将开启一个系列,每次聚焦5个可操作的.NET见解,每个见解都基于实际使用场景、性能考量和长期代码健康。没有空洞的理论,只有精准、简洁且能立即应用的技巧。

让我们进入今天的5个要点。

  1. 优先使用is not null而非!= null,实现更安全、更智能的空值检查 在启用了可空引用类型的现代C#中,编写if (user is not null)不仅仅是风格问题,它更安全、更清晰且更智能。

重要原因:

  • 编译器辅助的空值安全is not null模式改进了流分析。它帮助编译器更准确地确定变量的可空性,减少在可空感知上下文中的误报警告。
if (user is not null && user.IsActive)
{
    // 安全:编译器知道此处'user'不为空
}

对比之下:

if (user != null && user.IsActive)
{
    // 可能仍会引发可空警告
}
  • 不可变行为is not null不能被重载,而!= null在用户定义的类型中可以被重载。这使得is not null成为更可靠和可预测的选择。
class WeirdClass
{
    public static bool operator !=(WeirdClass? a, object? b) => false;
    public static bool operator ==(WeirdClass? a, object? b) => true;
}

var weird = new WeirdClass();

Console.WriteLine(weird != null);      // false — 误导性结果!
Console.WriteLine(weird is not null);  // true — 正确结果!

在启用可空引用类型的项目中,应始终优先使用is not null,尤其是在以下情况:

  • 你需要编译器强制的空值安全
  • 你正在检查可能重载==/!=的对象
  • 你在为重视可预测性和可读性的代码库做贡献
  1. 尽可能使用Parallel.For,但要知道何时避免使用它 Parallel.For可以通过在多个线程间分配工作来显著加快CPU密集型操作的速度。但它并非在任何情况下都是正确的选择。

适用场景:

Parallel.For(0, 1000, i =>
{
    // 繁重的CPU密集型任务,如加密或图像处理
    ProcessChunk(i);
});

这可以通过利用多个内核来减少执行时间。

应避免使用Parallel.For的情况:

  • 循环体涉及I/O操作,如文件或数据库操作。
  • 每次迭代的工作负载过轻(线程开销大于收益)。
  • 你需要严格的顺序或同步。

仅在以下情况使用Parallel.For

  • 迭代是独立的
  • 任务是CPU密集型的
  • 你不需要有序输出

最好对两种方式进行基准测试以确认。否则,为了清晰性和可预测性,坚持使用forforeach

  1. 在可能的情况下将Lambda捕获标记为static(C# 12) 在Lambda中捕获外部变量可能导致隐藏的内存分配。从C# 12开始,你可以将Lambda标记为static以消除捕获,使它们更快且无分配。
var ids = items.Select(static item => item.Id).ToArray();

static关键字确保不会创建闭包,并且如果意外访问任何外部变量,你会得到编译时错误。

  1. 使用元组解构,无需临时变量即可交换两个值 无需使用临时变量,你可以使用元组解构干净地交换两个变量:

示例:

int a = 5;
int b = 10;
// 使用元组交换
(a, b) = (b, a);

只要两边可赋值,这适用于任何类型。

为什么更好:

  • 无需额外的行或临时变量。
  • 清晰易读。
  • 没有像位运算XOR技巧那样的副作用或混淆。
  1. 了解何时避免使用await以获得更快的代码路径 如果可以直接返回Task而无需await,特别是在叶子级方法中,你可以避免async引入的状态机,从而使代码更快。
// 如果内部不需要await,这样更好
public Task<string> GetDataAsync() => httpClient.GetStringAsync(url);

对比之下:

// 由于async状态机,速度较慢
public async Task<string> GetDataAsync()
{
    return await httpClient.GetStringAsync(url);
}

这种细微的差异在高性能场景或紧密循环中会累积起来。每次你await一个Task时,在处理可能的结果切换过程中,底层会创建一个新类。如果不需要,何必添加这个额外的类呢?

这五个习惯可能看起来微不足道,但它们反映了对.NET底层工作原理的更深入理解。如果你在日常开发中采用其中一两个,你将开始在代码清晰度、性能和可维护性方面看到收益,并使自己成为更出色的.NET开发者。

相关留言评论
昵称:
邮箱:
阅读排行