Skip to main content
Guard clauses for argument validation with CallerArgumentExpression for automatic parameter name capture.

The Problem

Standard null checks are verbose and require nameof:
// BAD: Verbose and repetitive
public void Process(string? name, IService? service)
{
    _name = name ?? throw new ArgumentNullException(nameof(name));
    _service = service ?? throw new ArgumentNullException(nameof(service));
}

The Solution

// GOOD: Clean and expressive
public void Process(string? name, IService? service)
{
    _name = Guard.NotNull(name);
    _service = Guard.NotNull(service);
}
The parameter name is automatically captured - no nameof required.

Null Validation

NotNull

Validates non-null and returns the value:
// Throws ArgumentNullException if null
var validated = Guard.NotNull(possiblyNullValue);

// Can be used inline
ProcessItem(Guard.NotNull(item));

// In constructors
public MyClass(ILogger? logger, IConfig? config)
{
    _logger = Guard.NotNull(logger);
    _config = Guard.NotNull(config);
}

NotNullOrElse

Returns value or default (no exception):
// Reference types
var name = Guard.NotNullOrElse(user?.Name, "Anonymous");
var service = Guard.NotNullOrElse(optionalService, DefaultService.Instance);

// Value types
int count = Guard.NotNullOrElse(maybeCount, 0);

// Lazy evaluation for expensive defaults
var config = Guard.NotNullOrElse(optionalConfig, () => LoadDefaultConfig());

Member Validation

Validate an object and its member in one call:
// Validates both config and config.ConnectionString are non-null
public void Configure(Config? config)
{
    var connectionString = Guard.NotNullWithMember(config, config?.ConnectionString);
    // Throws ArgumentNullException if config is null
    // Throws ArgumentException if config.ConnectionString is null
}

// When the parent is known non-null, validate only the member
public void Process(Config config)
{
    var timeout = Guard.MemberNotNull(config, config.Timeout);
    // Throws ArgumentException if config.Timeout is null
}

String Validation

NotNullOrEmpty

Validates non-null and non-empty strings:
public void SetName(string? name)
{
    _name = Guard.NotNullOrEmpty(name);
    // Throws ArgumentNullException if null
    // Throws ArgumentException if empty
}

NotNullOrWhiteSpace

Validates strings contain meaningful content:
public void SetDescription(string? description)
{
    _description = Guard.NotNullOrWhiteSpace(description);
    // Throws if null, empty, or only whitespace
}

With Defaults

// Return default if null/empty
var displayName = Guard.NotNullOrEmptyOrElse(user.DisplayName, user.Username);

// Return default if null/empty/whitespace
var title = Guard.NotNullOrWhiteSpaceOrElse(article.Title, "Untitled");

// Lazy evaluation for expensive defaults
var config = Guard.NotNullOrEmptyOrElse(optionalConfig, () => LoadDefaultConfig());

String Length Validation

HasLength

Validates exact string length:
public void SetCountryCode(string? code)
{
    _code = Guard.HasLength(code, 2);  // ISO country codes
    // Throws ArgumentNullException if null
    // Throws ArgumentException if length != 2
}

HasMinLength / HasMaxLength

Validates minimum or maximum string length:
public void SetPassword(string? password)
{
    _password = Guard.HasMinLength(password, 8);
    // Throws if length < 8
}

public void SetUsername(string? username)
{
    _username = Guard.HasMaxLength(username, 50);
    // Throws if length > 50
}

HasLengthBetween

Validates string length is within a range (inclusive):
public void SetDisplayName(string? name)
{
    _name = Guard.HasLengthBetween(name, 3, 100);
    // Throws if length < 3 or length > 100
}

Collection Validation

NotNullOrEmpty

public void ProcessItems(IReadOnlyCollection<Item>? items)
{
    var validItems = Guard.NotNullOrEmpty(items);
    // Throws ArgumentNullException if null
    // Throws ArgumentException if empty

    foreach (var item in validItems)
    {
        // Process items
    }
}

NoDuplicates

Validates that a collection contains no duplicate elements:
public void SetIds(IEnumerable<int> ids)
{
    Guard.NoDuplicates(ids);
    // Throws ArgumentException with "Duplicate value found: {value}"
}

// IReadOnlyList overload returns the validated list
public void Process(IReadOnlyList<string>? names)
{
    var validNames = Guard.NoDuplicates(names);
    // Validates non-null AND no duplicates
}

// With custom equality comparer
public void SetNames(IEnumerable<string> names)
{
    Guard.NoDuplicates(names, StringComparer.OrdinalIgnoreCase);
    // Case-insensitive duplicate detection
}

Value Type Validation

NotDefault

Validates that a value type is not its default value:
public void SetId(Guid id)
{
    _id = Guard.NotDefault(id);
    // Throws ArgumentException if id == default(Guid)
}

public void SetTimestamp(DateTime timestamp)
{
    _timestamp = Guard.NotDefault(timestamp);
    // Throws if timestamp == default(DateTime)
}

NotEmpty (Guid)

Validates that a Guid is not empty:
public void SetUserId(Guid userId)
{
    _userId = Guard.NotEmpty(userId);
    // Throws ArgumentException if userId == Guid.Empty
}
NotEmpty for Guid is semantically clearer than NotDefault when working with identifiers, though they produce the same result for Guid.

Set Membership Validation

OneOf

Validates that a value is one of the allowed values:
public void SetProtocol(string protocol)
{
    _protocol = Guard.OneOf(protocol, new[] { "http", "https", "ftp" });
    // Throws ArgumentException if not in allowed set
}

// O(1) lookup with HashSet for large allowed sets
private static readonly HashSet<string> AllowedContentTypes = new(StringComparer.OrdinalIgnoreCase)
{
    "application/json",
    "application/xml",
    "text/plain"
};

public void SetContentType(string contentType)
{
    _contentType = Guard.OneOf(contentType, AllowedContentTypes);
}

NotOneOf

Validates that a value is not one of the disallowed values:
public void SetPort(int port)
{
    _port = Guard.NotOneOf(port, new[] { 0, 80, 443 });  // Reserved ports
    // Throws ArgumentException if in disallowed set
}

// O(1) lookup with HashSet
private static readonly HashSet<int> ReservedPorts = new() { 0, 80, 443, 8080 };

public void SetCustomPort(int port)
{
    _port = Guard.NotOneOf(port, ReservedPorts);
}

Range Validation

InRange

Validates value is within bounds (inclusive):
public void SetPage(int page)
{
    _page = Guard.InRange(page, 1, 100);
    // Throws ArgumentOutOfRangeException if outside range
}

// Works with any IComparable<T>
var temperature = Guard.InRange(temp, -40.0, 100.0);
var date = Guard.InRange(eventDate, DateTime.Today, DateTime.Today.AddYears(1));

ValidIndex

Validates array/list index:
public T GetItem(int index)
{
    Guard.ValidIndex(index, _items.Count);
    return _items[index];
}

Numeric Validation

All numeric validation methods use [MethodImpl(AggressiveInlining)] for hot paths and are available for int, long, double, and decimal types.

Basic Constraints

// Value cannot be zero
var divisor = Guard.NotZero(value);

// Value must be >= 0
var count = Guard.NotNegative(value);

// Value must be > 0
var quantity = Guard.Positive(value);

Boundary Constraints

// Upper bound (inclusive): value <= max
var percentage = Guard.NotGreaterThan(value, 100);

// Lower bound (inclusive): value >= min
var temperature = Guard.NotLessThan(value, -273.15);

// Upper bound (exclusive): value < max
var index = Guard.LessThan(value, array.Length);

// Lower bound (exclusive): value > min
var priority = Guard.GreaterThan(value, 0);

Double-Specific Validation

// Validates value is not NaN
var result = Guard.NotNaN(calculatedValue);

// Validates value is finite (not NaN or Infinity)
var coefficient = Guard.Finite(inputValue);
Double comparison methods (NotNegative, Positive, etc.) correctly handle NaN values by throwing ArgumentOutOfRangeException - NaN is not a valid number.

File System Validation

FileExists / DirectoryExists

Validates that a file or directory exists at the specified path:
public void LoadConfig(string? path)
{
    var validPath = Guard.FileExists(path);
    // Throws ArgumentNullException if null
    // Throws ArgumentException if empty or file doesn't exist
    var content = File.ReadAllText(validPath);
}

public void ProcessDirectory(string? path)
{
    var validPath = Guard.DirectoryExists(path);
    foreach (var file in Directory.GetFiles(validPath)) { }
}

Path Character Validation

// Validates filename contains no invalid characters
var fileName = Guard.ValidFileName(name);
// Throws if name contains characters like: \ / : * ? " < > |

// Nullable variant - returns null if input is null
var optionalName = Guard.ValidFileNameOrNull(name);

// Validates path contains no invalid characters
var validPath = Guard.ValidPath(userInput);

// Nullable variant
var optionalPath = Guard.ValidPathOrNull(userInput);

Extension Validation

// ValidExtension requires NO leading dot
var ext = Guard.ValidExtension("txt");      // Returns "txt"
Guard.ValidExtension(".txt");               // Throws - no leading dot allowed
Guard.ValidExtension("path/txt");           // Throws - no separators allowed

// NormalizedExtension accepts both and ensures leading dot
var ext1 = Guard.NormalizedExtension("txt");   // Returns ".txt"
var ext2 = Guard.NormalizedExtension(".txt");  // Returns ".txt"

Type Validation

DefinedEnum

Validates that an enum value is defined:
public void SetStatus(Status status)
{
    _status = Guard.DefinedEnum(status);
    // Throws ArgumentOutOfRangeException for undefined values like (Status)999
}

Type Constraints

// Validates type is not Nullable<T>
public void Register(Type type)
{
    Guard.NotNullableType(type);
    // Throws if type is int?, bool?, etc.
}

// Validates type implements/inherits from T
public void RegisterService(Type implementationType)
{
    Guard.AssignableTo<IService>(implementationType);
    // Throws if implementationType doesn't implement IService
}

// Validates T can be assigned to type
public void RegisterHandler(Type handlerType)
{
    Guard.AssignableFrom<BaseHandler>(handlerType);
    // Throws if BaseHandler isn't assignable to handlerType
}

Condition Validation

That

Validates a boolean condition:
public void SetAge(int age)
{
    Guard.That(age >= 0, "Age cannot be negative.");
    _age = age;
}

Satisfies

Validates value matches a predicate:
public void SetEmail(string email)
{
    _email = Guard.Satisfies(email, e => e.Contains('@'), "Invalid email format.");
}

// Complex validation
_config = Guard.Satisfies(config, c =>
    c.Timeout > TimeSpan.Zero && c.MaxRetries >= 0,
    "Invalid configuration values.");

Unreachable Code

Mark code paths that should never execute:
// In statement context
switch (status)
{
    case Status.Active: return "Active";
    case Status.Inactive: return "Inactive";
    default: Guard.Unreachable();
}

// In expression context (switch expressions)
return status switch
{
    Status.Active => "Active",
    Status.Inactive => "Inactive",
    _ => Guard.Unreachable<string>()
};

// With custom message
Guard.Unreachable("All enum values should be handled");

UnreachableIf

Throws only when a condition is true, useful for asserting invariants:
// Assert an invariant that should never be violated
Guard.UnreachableIf(list.Count < 0, "Count should never be negative");

// In validation logic
Guard.UnreachableIf(
    state == ProcessState.Running && !hasStarted,
    "Cannot be running without having started");
Unreachable and UnreachableIf automatically capture caller info (CallerMemberName, CallerFilePath, CallerLineNumber) to provide detailed exception messages for debugging.

Examples

Constructor Validation

public class OrderProcessor
{
    private readonly IOrderRepository _repository;
    private readonly IPaymentService _payment;
    private readonly ILogger _logger;

    public OrderProcessor(
        IOrderRepository? repository,
        IPaymentService? payment,
        ILogger? logger)
    {
        _repository = Guard.NotNull(repository);
        _payment = Guard.NotNull(payment);
        _logger = Guard.NotNull(logger);
    }
}

Method Parameter Validation

public async Task<Order> CreateOrderAsync(
    string? customerId,
    IReadOnlyCollection<OrderItem>? items,
    decimal discount)
{
    var validCustomerId = Guard.NotNullOrWhiteSpace(customerId);
    var validItems = Guard.NotNullOrEmpty(items);
    var validDiscount = Guard.InRange(discount, 0m, 100m);

    // All parameters validated
    return await ProcessOrder(validCustomerId, validItems, validDiscount);
}

Configuration Validation

public class ServerConfig
{
    public int Port { get; }
    public string Host { get; }
    public TimeSpan Timeout { get; }

    public ServerConfig(int port, string? host, TimeSpan timeout)
    {
        Port = Guard.InRange(port, 1, 65535);
        Host = Guard.NotNullOrWhiteSpace(host);
        Timeout = Guard.Satisfies(timeout, t => t > TimeSpan.Zero, "Timeout must be positive.");
    }
}

File Processing

public void ProcessFile(string? inputPath, string? outputFileName)
{
    // Validate input file exists
    var input = Guard.FileExists(inputPath);

    // Validate output filename is valid
    var output = Guard.ValidFileName(outputFileName);

    // Normalize the extension
    var ext = Guard.NormalizedExtension("txt");  // Returns ".txt"

    // Process...
}

// Optional filename validation (returns null if input is null)
public void SaveFile(string? fileName)
{
    var validName = Guard.ValidFileNameOrNull(fileName);
    if (validName is not null)
    {
        // Save with validated name
    }
}

API Reference

Null Validation

MethodThrowsDescription
NotNull&lt;T&gt;(value)ArgumentNullExceptionValidates non-null
NotNullOrElse&lt;T&gt;(value, default)-Returns value or default
NotNullOrElse&lt;T&gt;(value, Func&lt;T&gt;)-Lazy default evaluation
NotNullWithMember&lt;TParam, TMember&gt;(obj, member)ArgumentNullException, ArgumentExceptionValidates both object and member
MemberNotNull&lt;TParam, TMember&gt;(obj, member)ArgumentExceptionValidates member of non-null object

String Validation

MethodThrowsDescription
NotNullOrEmpty(string)ArgumentNullException, ArgumentExceptionValidates non-null, non-empty
NotNullOrWhiteSpace(string)ArgumentNullException, ArgumentExceptionValidates has content
NotNullOrEmptyOrElse(string, default)-Returns value or default
NotNullOrEmptyOrElse(string, Func<string>)-Lazy default evaluation
NotNullOrWhiteSpaceOrElse(string, default)-Returns value or default
NotNullOrWhiteSpaceOrElse(string, Func<string>)-Lazy default evaluation
HasLength(string, int)ArgumentNullException, ArgumentExceptionExact length
HasMinLength(string, int)ArgumentNullException, ArgumentExceptionMinimum length
HasMaxLength(string, int)ArgumentNullException, ArgumentExceptionMaximum length
HasLengthBetween(string, int, int)ArgumentNullException, ArgumentExceptionLength in range

Collection Validation

MethodThrowsDescription
NotNullOrEmpty&lt;T&gt;(collection)ArgumentNullException, ArgumentExceptionValidates collection
NoDuplicates&lt;T&gt;(IEnumerable&lt;T&gt;)ArgumentExceptionValidates no duplicates
NoDuplicates&lt;T&gt;(IEnumerable&lt;T&gt;, comparer)ArgumentExceptionWith custom equality
NoDuplicates&lt;T&gt;(IReadOnlyList&lt;T&gt;)ArgumentNullException, ArgumentExceptionValidates and returns list
NoDuplicates&lt;T&gt;(IReadOnlyList&lt;T&gt;, comparer)ArgumentNullException, ArgumentExceptionWith custom equality

Value Type Validation

MethodThrowsDescription
NotDefault&lt;T&gt;(T)ArgumentExceptionValue type not default
NotEmpty(Guid)ArgumentExceptionGuid is not Guid.Empty

Set Membership Validation

MethodThrowsDescription
OneOf&lt;T&gt;(T, T[])ArgumentExceptionValue in allowed set
OneOf&lt;T&gt;(T, HashSet&lt;T&gt;)ArgumentExceptionO(1) lookup variant
NotOneOf&lt;T&gt;(T, T[])ArgumentExceptionValue not in disallowed set
NotOneOf&lt;T&gt;(T, HashSet&lt;T&gt;)ArgumentExceptionO(1) lookup variant

File System Validation

MethodThrowsDescription
FileExists(path)ArgumentNullException, ArgumentExceptionValidates file exists
DirectoryExists(path)ArgumentNullException, ArgumentExceptionValidates directory exists
ValidFileName(name)ArgumentNullException, ArgumentExceptionNo invalid filename chars
ValidFileNameOrNull(name)ArgumentExceptionNullable variant
ValidPath(path)ArgumentNullException, ArgumentExceptionNo invalid path chars
ValidPathOrNull(path)ArgumentExceptionNullable variant
ValidExtension(ext)ArgumentNullException, ArgumentExceptionNo leading dot, no separators
NormalizedExtension(ext)ArgumentNullException, ArgumentExceptionEnsures leading dot

Type Validation

MethodThrowsDescription
DefinedEnum&lt;T&gt;(value)ArgumentOutOfRangeExceptionValidates enum is defined
NotNullableType(type)ArgumentNullException, ArgumentExceptionNot Nullable<T>
AssignableTo&lt;T&gt;(type)ArgumentNullException, ArgumentExceptionType implements T
AssignableFrom&lt;T&gt;(type)ArgumentNullException, ArgumentExceptionT assignable to type

Numeric Validation (int, long, double, decimal)

MethodThrowsDescription
NotZero(value)ArgumentOutOfRangeExceptionValue != 0
NotNegative(value)ArgumentOutOfRangeExceptionValue >= 0
Positive(value)ArgumentOutOfRangeExceptionValue > 0
NotGreaterThan(value, max)ArgumentOutOfRangeExceptionvalue ≤ max
NotLessThan(value, min)ArgumentOutOfRangeExceptionvalue ≥ min
LessThan(value, max)ArgumentOutOfRangeExceptionvalue < max
GreaterThan(value, min)ArgumentOutOfRangeExceptionvalue > min
InRange&lt;T&gt;(value, min, max)ArgumentOutOfRangeExceptionmin ≤ value ≤ max
ValidIndex(index, count)ArgumentOutOfRangeException0 ≤ index < count

Double-Specific

MethodThrowsDescription
NotNaN(value)ArgumentOutOfRangeExceptionValue is not NaN
Finite(value)ArgumentOutOfRangeExceptionNot NaN or Infinity

Condition Validation

MethodThrowsDescription
That(condition, message)ArgumentExceptionValidates condition
Satisfies&lt;T&gt;(value, predicate, message)ArgumentExceptionValidates with predicate
Unreachable(message?)InvalidOperationExceptionMarks unreachable code
Unreachable&lt;T&gt;(message?)InvalidOperationExceptionFor expression contexts
UnreachableIf(condition, message?)InvalidOperationExceptionThrows only when condition true
All validation methods use CallerArgumentExpressionAttribute to automatically capture the parameter name in exception messages - no nameof() required.