Using the GuardClause Library One-Pager

one-pagers csharp

Introduction

A guard clause is a software pattern that simplifies complex functions by "failing fast", checking for valid inputs up front and immediately failing if any are found.

It is an extremely useful and clean way to validate the method parameters of your library.

It helps avoid a bunch of If conditions with null/whitespace checks making the code look cleaner and easier to read.

Guard Clause against default values

This guard clause can be used if you would like to throw an exception in case the default value of the value type or object is passed in.

//The following guards execute successfully.
Guard.Against.Default("", parameterName: "string", message: "Default string value passed");
Guard.Against.Default(1, parameterName:"int", message: "Default int value passed");
Guard.Against.Default(Guid.NewGuid(), "guid", "Default guid value passed");
Guard.Against.Default(DateTime.Now, "datetime", "Default datetime value passed");
Guard.Against.Default(new Object(), "object"); // You can omit the error message, you get back a default error message

//The following guards throw an ArgumentException
Guard.Against.Default(
    default(string),
    parameterName: "string",
    message: "Default value for string passed."
);
Guard.Against.Default(default(int), "int");
Guard.Against.Default(default(Guid), "guid");
Guard.Against.Default(default(DateTime), "datetime");
Guard.Against.Default(default(DateTime), "datetime");

Guard Clauses against an expression

//When the expression evaluates to true, do nothing
Guard.Against.AgainstExpression((x) => x == 10, test, "Value is not equal to 10");

//When the expression evaluates to false, throw an ArgumentException
Guard.Against.AgainstExpression((x) => x == 5, test, "Value is not equal to 10");

Custom Guard Clause

public static class FooGuard
{
    public static void Foo(this IGuardClause guardClause, string input, [CallerArgumentExpression("input")] string? parameterName = null)
    {
        if (input?.ToLower() == "foo")
            throw new ArgumentException("Should not have been foo!", parameterName);
    }
}
//Works fine
Guard.Against.Foo("anythingElse", "aParameterName");

//ArgumentException - should not have been foo
Assert.Throws<ArgumentException>(() => Guard.Against.Foo("foo", "aParameterName"));

Guard Clauses against invalid data format using regular expressions

//Works fine
[Theory]
[InlineData("12345", @"\d{1,6}")]
[InlineData("50FA", @"[0-9a-fA-F]{1,6}")]
[InlineData("abfACD", @"[a-fA-F]{1,8}")]
[InlineData("DHSTRY", @"[A-Z]+")]
[InlineData("3498792", @"\d+")]
public void ReturnsExpectedValueGivenCorrectFormat(string input, string regexPattern)
{
    var result = Guard.Against.InvalidFormat(input, nameof(input), regexPattern);
    Assert.Equal(input, result);
}

//Throws an ArgumentException
[Theory]
[InlineData("aaa", @"\d{1,6}")]
[InlineData("50XA", @"[0-9a-fA-F]{1,6}")]
[InlineData("2GudhUtG", @"[a-fA-F]+")]
[InlineData("sDHSTRY", @"[A-Z]+")]
[InlineData("3F498792", @"\d+")]
[InlineData("", @"\d+")]
public void ThrowsGivenGivenIncorrectFormat(string input, string regexPattern)
{
    Assert.Throws<ArgumentException>(() => Guard.Against.InvalidFormat(input, nameof(input), regexPattern));
}

Guard Clauses against negative or zero values

//Works fine
Guard.Against.NegativeOrZero(1, "intPositive");
Guard.Against.NegativeOrZero(1L, "longPositive");
Guard.Against.NegativeOrZero(1.0M, "decimalPositive");
Guard.Against.NegativeOrZero(1.0f, "floatPositive");
Guard.Against.NegativeOrZero(1.0, "doublePositive");
Guard.Against.NegativeOrZero(TimeSpan.FromSeconds(1), "timespanPositive");

//Throws ArgumentException
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(0, "intZero"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(0L, "longZero"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(0M, "decimalZero"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(0f, "floatZero"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(0.0, "doubleZero"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(TimeSpan.Zero, "timespanZero"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-1, "intNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-42, "intNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-1L, "longNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-456L, "longNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-1M, "decimalNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-567M, "decimalNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-1f, "floatNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-4567f, "floatNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-1.0, "doubleNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(-456.453, "doubleNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(TimeSpan.FromSeconds(-1), "timespanNegative"));
Assert.Throws<ArgumentException>(() => Guard.Against.NegativeOrZero(TimeSpan.FromSeconds(-456), "timespanNegative"));

Guard Clause against null

//Works fine
Guard.Against.Null("", "string");
Guard.Against.Null(1, "int");
Guard.Against.Null(Guid.Empty, "guid");
Guard.Against.Null(DateTime.Now, "datetime");
Guard.Against.Null(new Object(), "object");

//throws exception
Assert.Throws<ArgumentNullException>(() => Guard.Against.Null(obj, "null"));

Guard Clause against null or empty

//Works fine
Guard.Against.NullOrEmpty("a", "string");
Guard.Against.Empty("a".AsSpan(), "stringSpan");
Guard.Against.NullOrEmpty("1", "aNumericString");
Guard.Against.NullOrEmpty(new[] { "foo", "bar" }, "stringArray");
Guard.Against.NullOrEmpty(new[] { 1, 2 }, "intArray");
Guard.Against.NullOrEmpty(Guid.NewGuid(), "guid");

//Throws Exception
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrEmpty("", "emptyString"));
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrEmpty(Guid.Empty, "emptyGuid"));

string? nullString = null;
Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrEmpty(nullString, "nullString"));

Guid? nullGuid = null;
Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrEmpty(nullGuid, "nullGuid"));

Guard Clause against null or invalid input

In this case you can pass a predicate function that checks if the input is valid are return true or false accordingly.

var input = "Test"
Assert.Throws<ArgumentNullException>("string",
            () => Guard.Against.NullOrInvalidInput(input, "stringParameterName", (x => x.Length > 10)));

Guard Clause against null or white space

//Works fine
[Theory]
[InlineData("a")]
[InlineData("1")]
[InlineData("some text")]
[InlineData(" leading whitespace")]
[InlineData("trailing whitespace ")]
public void DoesNothingGivenNonEmptyStringValue(string nonEmptyString)
{
    Guard.Against.NullOrWhiteSpace(nonEmptyString, "string");
    Guard.Against.WhiteSpace(nonEmptyString.AsSpan(), "stringSpan");
    Guard.Against.NullOrWhiteSpace(nonEmptyString, "aNumericString");
}


//Throws Exception
Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrWhiteSpace(null, "null"));
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrWhiteSpace("", "emptystring"));
[Theory]
[InlineData(" ")]
[InlineData("   ")]
public void ThrowsGivenWhiteSpaceString(string whiteSpaceString)
{
    Assert.Throws<ArgumentException>(() => Guard.Against.NullOrWhiteSpace(whiteSpaceString, "whitespacestring"));
    Assert.Throws<ArgumentException>(() => Guard.Against.WhiteSpace(whiteSpaceString.AsSpan(), "whiteSpaceStringSpan"));
}

Guard against our of range values

The Guard.Against.OutOfRange method can be used to check if the values are out of a specific range of numbers or dates or Enums and IEnumerable.

References