guard clause是编码模式而非语法特性,ardalis.guardclauses是最轻量实现;正确使用需传nameof(x)而非字符串字面量,区分nullorwhitespace与nullorempty语义,outofrange注意参数顺序和类型限制,expression仅用于纯参数合法性校验且无副作用。

Guard Clause 不是语法特性,而是编码模式;Ardalis.GuardClauses 是它最轻量、最直接的实现。用不用它,取决于你是否愿意在方法入口就“拦住”非法输入,而不是让错误潜入业务逻辑深处再爆炸。
Guard.Against.Null 为什么总报错参数名不对
常见错误现象是抛出 ArgumentNullException 但 paramName 显示为 "value" 或 "input",而不是你期望的 "user" 或 "id"。根本原因是没传 nameof(x),而是传了字符串字面量或变量值。
- ✅ 正确写法:
Guard.Against.Null(user, nameof(user)) - ❌ 错误写法:
Guard.Against.Null(user, "user")(硬编码字符串,重构时不会同步更新) - ❌ 错误写法:
Guard.Against.Null(user, user.ToString())(运行时报NullReferenceException)
这个参数必须是编译期确定的标识符名称,nameof 是唯一安全的选择。IDE 重命名时它会自动更新,这是它不可替代的关键点。
NullOrWhiteSpace 和 NullOrEmpty 的实际区别
两者都用于字符串,但语义和触发条件不同,选错会导致逻辑漏洞。
-
Guard.Against.NullOrWhiteSpace(name, nameof(name)):拒绝null、空字符串""、以及只含空白字符(如"\t \n")的字符串 -
Guard.Against.NullOrEmpty(name, nameof(name)):只拒绝null和"",允许" "、"\r\n"这类纯空白字符串通过
用户输入场景(比如表单提交)应优先用 NullOrWhiteSpace;配置项或协议字段若明确允许空白占位符,则考虑 NullOrEmpty。别凭感觉混用,它们抛出的异常类型相同,但语义边界完全不同。
OutOfRange 检查整数和集合时的陷阱
Guard.Against.OutOfRange 看似简单,但参数顺序和类型容易踩坑:
- 检查单个值:
Guard.Against.OutOfRange(age, 0, 150, nameof(age))—— 第二、三个参数是minInclusive和maxInclusive - 检查集合长度:
Guard.Against.OutOfRange(items, 1, 100, nameof(items))—— 这里检查的是items.Count,不是元素值 - 不要对
int?直接调用OutOfRange,需先解包或改用Null+OutOfRange组合
它不支持浮点数范围检查(如 decimal),对 decimal price 应改用 Guard.Against.Negative(price, ...) 或 Expression 自定义。
自定义 Guard 表达式怎么写才不绕晕
内置方法覆盖不了所有逻辑时,Guard.Against.Expression 是兜底方案,但写法容易失控:
- ✅ 推荐写法:
Guard.Against.Expression(x => x.Length 20, username, nameof(username), "用户名长度必须在3~20个字符之间") - ❌ 避免写法:
Guard.Against.Expression(x => !IsValidUsername(x), ...)—— 把验证逻辑藏进外部方法,失去内联可读性 - ❌ 危险写法:
Guard.Against.Expression(x => x.Contains("<script>"), ...)</script>—— 这属于业务规则/安全过滤,不该放在 Guard 层,应移至领域服务或管道中间件
Guard 的职责永远只是“参数合法性”,不是“业务合规性”。越界检查、枚举范围、非空约束是它的地盘;SQL 注入防护、密码强度、权限校验不是。
最常被忽略的一点:Guard 方法本身不参与业务流程控制,也不该有副作用。如果你在 Guard.Against 调用里写了日志、发消息、查数据库——那已经违背了 Guard Clause 的设计本意。它必须是纯判断、零 IO、零状态变更的“守门人”。











