LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

5个被严重低估的 C# 特性,让防御性代码彻底消失

admin
2026年3月4日 7:49 本文热度 107

很多老派 .NET 开发者都有一个共同习惯:写一堆防御性代码。

方法开头先来三行 null 检查,构造函数里各种参数验证,属性全部 private set “以防万一”,单元测试也围绕“不变量有没有被破坏”反复验证。

但说实话,这些代码大多不是业务逻辑,而是对“可能出错”的恐惧。

从 C# 10 到 C# 13,语言本身已经进化到一个新的阶段。很多过去只能在运行时做的防御,现在可以在编译期直接解决。这不是语法糖,而是类型系统和流分析能力的提升。

下面这 5 个特性,如果用好,能明显减少守卫代码,让系统更清晰、更可靠。


1. required 成员:把不变量交给编译器

过去我们经常会写出这样的代码:

var user = new User { Name = "Ali" }; // Email 被遗漏,编译器却无动于衷

如果 Email 是业务必须字段,这种“半初始化对象”迟早会在生产环境炸锅。

C# 11 引入了 required 成员:

public sealed class User
{
    public required string Name { get; init; }
    public required string Email { get; init; }
}

只要少写一个属性,编译器立刻报错。

这背后的价值很大:让无效状态无法被表示

你不再需要在每个方法里写:

if (string.IsNullOrWhiteSpace(user.Email)) ...

因为类型已经保证它存在。

官方说明见: ① https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#required-members


2. init-only 属性:真正的“构造后不可变”

很多人以为 private set 就是不可变:

public class Order
{
    public DateTime CreatedAt { getprivate set; }

    public void Recalculate()
        => CreatedAt = DateTime.UtcNow; // 其实还能改
}

这其实是假不可变。类内部随时可以改。

init 才是真正的生命周期约束:

public sealed class Order
{
    public required DateTime CreatedAt { get; init; }
}

对象初始化之后,再赋值就会直接编译错误:

order.CreatedAt = DateTime.UtcNow.AddDays(-1); // 编译失败

这带来的不是“语法优雅”,而是对象图稳定性。尤其在高并发系统中,不可变对象意味着线程安全更简单,行为更可预测。

官方文档: ② https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init


3. ConfigureAwaitOptions:异步语义变得可控

过去我们只有:

await task.ConfigureAwait(false);

很多人用它,但并不真正理解它。

.NET 8 引入了 ConfigureAwaitOptions,让意图更明确。例如:

await SomeWork().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);

在管道或框架级代码中,这非常有用:

public async Task ExecuteAsync(Func<Task> handler)
{
    try
    {
        await handler().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Pipeline failure.");
    }
}

你可以清楚表达“异常如何传播”“是否继续执行”等语义,而不是靠约定。

相关说明可参考 .NET 博客文章: ③ https://devblogs.microsoft.com/dotnet/


4. CallerArgumentExpression:让守卫代码自解释

传统参数检查通常这样写:

if (email == null)
    throw new ArgumentNullException(nameof(email));

错误信息基本靠 nameof 拼接。

C# 11 提供了 CallerArgumentExpression,可以自动捕获调用表达式:

public static void NotEmpty(
    string value,
    [CallerArgumentExpression(nameof(value
))] string? expression
 = null)
{
    if (string.IsNullOrWhiteSpace(value))
        throw new ArgumentException($"Invalid value: {expression}");
}

调用:

NotEmpty(user.Email);

异常信息会自动变成:

Invalid value: user.Email

这在验证库中尤其好用:

Ensure.NotNull(order.Items);
Ensure.NotEmpty(order.Customer.Email);

守卫逻辑变得更干净,错误信息也更有上下文。

官方文档: ④ https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerargumentexpressionattribute


5. 可空性流属性:告诉编译器你的真实意图

开启可空引用类型之后,我们经常遇到这种情况:

if (TryGetUser(id, out var user))
{
    user.DoSomething(); // 编译器仍然警告
}

虽然逻辑上是安全的,但编译器不知道。

这时候可以用 [NotNullWhen(true)]

public static bool TryGetUser(
    int id,
    [NotNullWhen(true
)] out User? user)

{
    // ...
}

现在,只要返回 true,编译器就相信 user 不为 null。

这对于公共库尤其重要,可以大幅减少 API 使用者的冗余 null 检查。

官方说明: ⑤ https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis


防御性代码 vs 声明式意图

传统方式
现代方式
运行时校验
编译期保障
手动守卫子句
流感知属性
可变对象
生命周期约束对象
异步经验主义
显式配置
字符串错误消息
表达式上下文

防御性代码从来不是目标,目标是正确性。

现代 C# 做的事情很简单:把“你希望成立的条件”写进类型系统,而不是写进 if 语句。


综合示例:五个特性协同工作

public sealedclassPayment
{
    public required Guid Id { get; init; }
    public required decimal Amount { get; init; }
    public required string Currency { get; init; }

    public static bool TryCreate(
        decimal amount,
        string currency,
        [NotNullWhen(true
)] out Payment? payment)

    {
        if (amount <= 0 || string.IsNullOrWhiteSpace(currency))
        {
            payment = null;
            returnfalse;
        }

        payment = new Payment
        {
            Id = Guid.NewGuid(),
            Amount = amount,
            Currency = currency
        };
        returntrue;
    }
}

你会发现,这里没有:

没有后续 null 检查。 没有对象构造后的二次校验。 没有防止属性被篡改的守卫逻辑。

因为类型已经表达了不变量。


架构层面的影响

当团队一致使用这些特性时,你会看到一些很明显的变化:

DTO 初始化错误明显减少。 空引用异常几乎消失。 守卫库代码越来越少。 单元测试更专注业务行为,而不是参数校验。

这不是“代码看起来更现代”,而是结构上的简化。系统规模越大,这种简化的复利效应越明显。


结语

现代 C# 的核心思想是:

不要假设开发者能记住所有不变量。 让语言帮你记住。

当类型系统足够精确,运行时就不需要那么多防御。

代码不只是能编译,而是能长期演进、能在规模下稳定运行。这才是语言进化真正的意义。


参考资料

① Microsoft. C# 11 required membershttps://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#required-members

② Microsoft. Init-only settershttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init

③ Microsoft. .NET Bloghttps://devblogs.microsoft.com/dotnet/

④ Microsoft. CallerArgumentExpressionAttributehttps://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerargumentexpressionattribute

⑤ Microsoft. Nullable flow attributeshttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis

⑥ Microsoft. .NET Bloghttps://devblogs.microsoft.com/dotnet/


阅读原文:原文链接


该文章在 2026/3/4 9:49:00 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved