Search code examples
c#.netgenericscomparenumeric

Guard against arguments that are out of range


I'm looking for a way to guard against arguments that are out of range, something along the lines of...

public static class Guard {

    public static void AgainstOutOfRange<TArgument>(TArgument argument,
        TArgument minInclusive, TArgument maxInclusive)
        where TArgument : struct, IComparable<TArgument>, IConvertible
    {
        if(argument.CompareTo ????
    }

Looking for 'numeric constraints' on generics I stumpled upon this where @Lee suggests

public static bool IsGreaterThan<T>(this T actual, T comp) where T : IComparable<T>
{
    return actual.CompareTo(comp) > 0;
}

I'm however not sure if this is inclusive, exclusive and how to check for 'IsSmallerThan' (I do not fully understand IComparable yet)


Solution

    • Inclusive means that we're going to include the lower and upper bounds as valid values. For example, Guard.AgainstOutOfRange(5, 0, 5) should accept 5 as a valid argument, since we're allowing 0 to 5 inclusive.
    • Exclusive means that we're going to exclude the lower and upper bounds. For example, Guard.ArgumentOutOfRange(5, 0, 5) should reject 5 as a valid argument, since we're not allowing the upper and lower bounds themselves as valid values.

    With that in mind, I think you're just looking for:

    if(argument.CompareTo(minInclusive) < 0 ||
        argument.CompareTo(maxInclusive) > 0)
    {
        throw new ArgumentException("Argument out of range");
    }
    

    Here we're saying:

    • If the result of comparing argument to minInclusive is less than zero (argument is less than minInclusive) OR
    • The result of comparing argument to maxInclusive is greater than zero (argument is greater than maxInclusive)

    ... Then we have an invalid argument. Note that if the comparison is equal to zero in either case, we treat that as an valid argument (zero is not greater than or less than zero, so it passes our test).

    If we were to make our test exclusive, we could include the result of CompareTo equaling zero in our check:

    if(argument.CompareTo(minInclusive) <= 0 ||
        argument.CompareTo(maxInclusive) >= 0)
    {
        throw new ArgumentException("Argument out of range");
    }
    

    Then we'd be saying if the parameter is equal to the lower or upper bound, that's an invalid argument too.

    The documentation for IComparable.CompareTo explains the result of instance.CompareTo(obj):

    Less than zero This instance precedes obj in the sort order.

    Zero: This instance occurs in the same position in the sort order as obj.

    Greater than zero: This instance follows obj in the sort order.