7个“危险”高效的C#特性:让冗余代码彻底消失

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

🧠 为什么这篇博客很重要

你的工作不是写setter、空值检查或try-catch-finally,但大多数C#代码库却逼着你做这些。

现代C#(9-13版本)通过以下特性消除了这些样板代码:

  • 编译器强制的契约
  • 运行时优化的行为
  • 微软背书的模式(在Azure和Copilot中使用)

本文将介绍7个“危险”高效的特性,让你删掉那些本就不该存在的代码。

🎞️ 深入探讨前:先看看痛点(和解决方案)

🔐 1. required + init:告别构造函数

这对组合让你在编译时快速发现错误,无需构造函数、魔术字符串和空值,就能构建DTO和配置。

✅ 之前的写法:

public class User {
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public User(string first, string last) {
        FirstName = first;
        LastName = last;
    }
}

✅ 之后的写法(C# 11+):

public class User {
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    // 编译时检查必填项,属性不可变
}

🧠 无需手动验证或构造函数。缺少必填属性会在编译时直接报错——这是最佳的错误发现时机。

📌 参考:required修饰符 — Microsoft Docs

🧠 2. 模式匹配:比if更智能

模式匹配已全面升级:

  • 类型模式
  • 属性模式
  • 递归和列表模式(C# 12+)

✅ 实际API示例:

return request.Method switch {
    "GET" => HandleGet(request),
    "POST" => HandlePost(request),
    _ => Results.BadRequest()
};

✅ 属性模式:

if (person is Employee { Salary: > 100_000 }) {
    GiveBonus(person);
}

✅ 列表模式:

if (nums is [_, _, 42, ..])
    Console.WriteLine("在第三个位置找到42。");

🧠 借助深层模式,你甚至可以匹配嵌套的对象结构和集合——无需空值链和类型检查。

📌 参考:模式匹配 — Microsoft Docs

🧬 3. record + with:不可变的清晰表达

想要100%类型安全、不可变的数据模型,又不想用AutoMapper或反射?

试试record和with。

✅ 之前的写法:

var updated = new Order {
    Id = original.Id,
    Customer = original.Customer,
    Status = "Shipped"
};

✅ 之后的写法:

var updated = original with { Status = "Shipped" };

✔ 无需映射工具 ✔ 无易变性bug ✔ 语言内置的复制语义

📌 参考:record类型 — Microsoft Docs

🔥 4. InterpolatedStringHandler:零分配日志

.NET 6为日志添加了编译器魔法: 如果日志级别被禁用,插值字符串甚至不会被计算——无分配,无性能损耗。

✅ 不推荐:

_logger.LogDebug($"Order {order.Id} processed for {order.Customer}");

✅ 推荐:

_logger.LogDebug("Order {OrderId} processed for {Customer}", order.Id, order.Customer);

✅ 快速 ✅ 结构化 ✅ 零插值成本

🧠 Microsoft.Extensions.Logging在幕后使用InterpolatedStringHandler优化日志格式化——而LoggerMessage.Define()通过预编译委托让这一过程更快。

📌 参考:InterpolatedStringHandler — Microsoft Docs

🧩 5. CallerArgumentExpression:告别nameof()

抛出异常时,想让参数名自动填充?

✅ 定义一次:

public static void ThrowIfNull<T>(
    T argument,
    [CallerArgumentExpression("argument")] string? name = null)
    => _ = argument ?? throw new ArgumentNullException(name);

✅ 随处使用:

ThrowIfNull(user); // 异常信息:“值不能为 null。(参数 'user')”

🧠 一个可重用的辅助方法,替代数十个繁琐的nameof()调用。

📌 参考:CallerArgumentExpression — Microsoft Docs

⚙️ 6. await using:无痛异步清理

如果你的类型实现了IAsyncDisposable,这应该成为你的新默认写法。

✅ 之前的写法:

var conn = await factory.CreateAsync();
try {
    await conn.SendAsync(...);
}
finally {
    await conn.DisposeAsync();
}

✅ 之后的写法:

await using var conn = await factory.CreateAsync();
await conn.SendAsync(...);

🧠 尤其在Blazor、EF Core和ASP.NET中非常有用,这些场景中异步流或DbContext很常见。

在Entity Framework Core的DbContext中使用时,可防止异步泄漏并提高负载下的性能。

📌 参考:IAsyncDisposable — Microsoft Docs

⚡ 7. 源生成器:替代反射,提升性能

既然可以在构建时生成代码,何必在运行时反射?

微软在以下组件中使用了源生成器:

  • System.Text.Json
  • Microsoft.Extensions.Logging
  • EF Core元数据

✅ 示例:JSON源生成

[JsonSerializable(typeof(Order))]
internal partial class OrderJsonContext : JsonSerializerContext { }

🧠 这避免了ASP.NET中的反射,并在AOT场景中减小了输出大小。

需要在csproj中设置JsonSourceGenerationMode或通过JsonSerializerContext设置。

<ItemGroup>
  <PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>

📌 参考:System.Text.Json源生成 — Microsoft Docs

🎁 bonus:file修饰符 = 真正的文件作用域类型

想要一个仅对当前文件可见的辅助类?

✅ 这样写:

file class Helper {
    // 无法从项目的其他地方访问
}

🧠 非常适合内部静态辅助工具、小型DSL或测试脚手架。

📌 参考:file作用域类型 — C# 12 Docs

✅ 摘要表

(原内容未提供具体表格内容,此处保持原样)

🚀 最终挑战:从你的应用中删除100行代码

✅ 下一个PR建议:

  • 用record + required + with重构一个DTO
  • 用CallerArgumentExpression替代一个nameof()辅助方法
  • 把一个try-finally换成await using

💡 然后运行差异对比。看着样板代码消失——且不会破坏任何测试。

🙋 轮到你了

这些特性中,你已经在使用哪些? 你会在下一个项目中重构哪一个?

👉 留下评论或分享给你的团队。 因为简洁、富有表现力的现代C#并不“危险”——而是高效到“致命”。

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