CleanArch.DevKit.Guards
1.1.1
dotnet add package CleanArch.DevKit.Guards --version 1.1.1
NuGet\Install-Package CleanArch.DevKit.Guards -Version 1.1.1
<PackageReference Include="CleanArch.DevKit.Guards" Version="1.1.1" />
<PackageVersion Include="CleanArch.DevKit.Guards" Version="1.1.1" />
<PackageReference Include="CleanArch.DevKit.Guards" />
paket add CleanArch.DevKit.Guards --version 1.1.1
#r "nuget: CleanArch.DevKit.Guards, 1.1.1"
#:package CleanArch.DevKit.Guards@1.1.1
#addin nuget:?package=CleanArch.DevKit.Guards&version=1.1.1
#tool nuget:?package=CleanArch.DevKit.Guards&version=1.1.1
CleanArch.DevKit.Guards
Static guard-clause utility for defensive argument validation in .NET 10. Returns the validated value for assignment chaining, captures parameter names automatically via [CallerArgumentExpression], throws only standard BCL exception types. No dependencies beyond the BCL.
Part of the CleanArch.DevKit set — standalone, no Mediator coupling.
Install
dotnet add package CleanArch.DevKit.Guards
Quick start
using CleanArch.DevKit.Guards;
public sealed class Order
{
private readonly string _customerName;
private readonly decimal _total;
private readonly OrderStatus _status;
public Order(string customerName, decimal total, OrderStatus status, IEnumerable<OrderLine> lines)
{
// Returns the value, so you can assign inline.
_customerName = Guard.NotNullOrWhiteSpace(customerName);
_total = Guard.NotNegative(total);
_status = Guard.EnumDefined(status);
// Throws if lines is null or has zero elements; iterates at most once.
Guard.NotNullOrEmpty(lines);
// Predicate guards take a value, an "is invalid" predicate, and a message.
Guard.Against(lines, x => x.Count() > 100, "Order cannot exceed 100 lines.");
}
}
Parameter names are captured automatically — you never write nameof(...):
public void Process(Order order)
{
Guard.NotNull(order);
// -> throws ArgumentNullException with ParamName = "order"
}
API reference
| Method | Throws | Use case |
|---|---|---|
NotNull<T>(T?) (class) |
ArgumentNullException |
Reject null reference arguments. |
NotNull<T>(T?) (struct) |
ArgumentNullException |
Reject null Nullable<T>; returns the underlying value. |
NotDefault<T>(T) |
ArgumentException |
Reject default(T) — e.g. Guid.Empty, DateTime.MinValue, 0. |
NotNullOrEmpty(string?) |
ArgumentException |
Non-null, non-empty string (whitespace allowed). |
NotNullOrWhiteSpace(string?) |
ArgumentException |
Non-null, non-empty, non-whitespace string. |
NotNegative<T>(T) |
ArgumentOutOfRangeException |
T : INumber<T>, value ≥ 0. |
NotNegativeOrZero<T>(T) |
ArgumentOutOfRangeException |
T : INumber<T>, value > 0. |
NotZero<T>(T) |
ArgumentOutOfRangeException |
T : INumber<T>, value ≠ 0. |
InRange<T>(T, min, max) |
ArgumentOutOfRangeException |
T : INumber<T>, value within inclusive [min, max]. |
GreaterThan<T>(T, other) |
ArgumentOutOfRangeException |
T : INumber<T>, value strictly greater than other. |
LessThan<T>(T, other) |
ArgumentOutOfRangeException |
T : INumber<T>, value strictly less than other. |
NotNullOrEmpty<T>(IEnumerable<T>?) |
ArgumentException |
Non-null, at least one element. |
EnumDefined<T>(T) |
ArgumentOutOfRangeException |
T : struct, Enum, value is declared. |
Against<T>(T, Func<T,bool>, string) |
ArgumentException |
Custom predicate — convention: true means invalid. |
Note:
NotNullOrEmpty(string?),NotNullOrWhiteSpace(string?), andNotNullOrEmpty<T>(IEnumerable<T>?)throwArgumentExceptionfor both null and empty inputs — notArgumentNullExceptionfor null. If you need to distinguish, useGuard.NotNull(...)first.
Every method returns the validated value, enabling field-init chaining:
public sealed class Money
{
private readonly decimal _amount;
public Money(decimal amount) => _amount = Guard.NotNegative(amount);
}
FAQ
Why no Result-based API?
This package is for defensive validation at function entry. Result-based handling is for command/query layers — use CleanArch.DevKit.Mediator.Results with a ValidationBehavior for that. A Result-returning guard would mix two distinct responsibilities.
Why no extensibility hook? A hook (interface, partial class, extension methods) implies a contract this package would commit to. We don't yet know what that contract should look like and adding one prematurely locks us in. For domain-specific guards, write your own static class — it stays cohesive with your domain types and avoids a layering surprise.
Why no customMessage overloads on built-in guards?
The standard messages are deliberate. If you need a different message, write throw new ArgumentException(...) directly — at that point the guard helper isn't buying you much. Against is the only guard that takes a message because its predicate is opaque to the helper.
Why no email/regex/URI guards?
Format validation is too policy-specific (which RFC? which regex? case-sensitive?). Use System.Net.Mail.MailAddress or Uri.TryCreate directly — they're more accurate than any regex this package could ship.
Why INumber<T> rather than int / decimal overloads?
One generic method covers every numeric BCL type (int, long, short, byte, decimal, double, float, Half, BigInteger, nint, nuint, and any custom type implementing INumber<T>). The JIT specializes per type, so performance matches type-specific overloads.
License
MIT. Copyright © 2026 Mickaël FRANCOIS.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.