3
\$\begingroup\$

I'm writing a simple set/framework of extension methods for argument/parameter validation. What I'm mostly concerned about, ATM is the performance, against the simpliest boilerplate

if(obj == null)
   throw new ArgumentNullException()

The framework I came up with is like that: The Validatable class packing the base object that will be validated through the ThrowIf() extension method.

public class Validatable<T>
{
    public T Value { get; }
    public Validatable(ref T argument)
    {
        Value = argument;
    }
}


public static Validatable<T> ThrowIf<T>(this T argument)
{
    return new Validatable<T>(ref argument);
}

And test methods themselves:

public static Validatable<T> IsNull<T>(this Validatable<T> argument) where T : class
{
    if (argument.Value == null)
        throw new ArgumentNullException();
    return argument;
}

public static Validatable<T> CollectionEmpty<T>(this Validatable<T> argument) where T: ICollection
{
    if (argument.Value.Count == 0)
        throw new ArgumentException();
    return argument;
}

This allows me to validate items with simple:

List<int> testList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
testList.ThrowIf().IsNull().CollectionEmpty();

Everything was fine untill I decided to test, how it fares against the simple boilerplate.

int iNumber = 1000000000;
Stopwatch sw1 = new Stopwatch();
Stopwatch sw2 = new Stopwatch();
sw1.Start();
for (int i = 0; i < iNumber; ++i)
{
    if (testList == null)
        throw new ArgumentNullException();
    if (testList.Count == 0)
        throw new ArgumentException();
}
sw1.Stop();
sw2.Start();
for (int i = 0; i < iNumber; ++i)
{
    testList.ThrowIf().IsNull().CollectionEmpty();
}
sw2.Stop();

Console.WriteLine($"sw1: {sw1.ElapsedTicks} ticks");
Console.WriteLine($"sw2: {sw2.ElapsedTicks} ticks");

and the output was:

sw1: 10720019 ticks

sw2: 161726524 ticks

That's an order of magnitute difference. It was over 1,000,000,000 iterations, but still the difference is there.

I also tried with

public static void IsNullLite<T>(this T argument)
{
    if (argument == null)
        throw new ArgumentException();
}

public static void CollectionEmpty2Lite<T>(this T argument) where T : ICollection
{
    if (argument.Count == 0)
        throw new ArgumentException();
}

And it's result came somewhere in the middle with

sw3: 84165637 ticks

My questions are:

  1. Should I be concerned with the performance loss. It was +- 3sec vs 47secs on my PC, at 1,000,000,000 iterations. I do not plan to run it in such loops, over so many objects, but I was going to use it my game engine, where I have about 16ms per Update tick, to do much more than just validate some variables.
  2. Is there a better/faster way of doing it. Preferably, I'd like to pack the validation object into some sort of Validatable class, to not clutter the intelisense with dozens of validation methods.
  3. Is there some error on my part, in the way I tested it? I ran the tests in Debug mode with code optimalization turned off.
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$
public static Validatable<T> ThrowIf<T>(this T argument)
{
    return new Validatable<T>(ref argument);
}

This of course is creating a lot of objects. You could speed this a little bit up by making Validatable a struct but still, it'll be slower then the normal check. Convenience comes often with some kind of a performance hit.


I was going to use it my game engine

You'd better not. Make it fast, not convenient.

There is one more thing about validating arguments. The only thing you need to validate is user input or something that you cannot predict like configurations. Internally most methods usually work without argument checking because it's nearly impossible to pass invalid values. Use Debug.Assert for this purpose during development to be absolutely sure if you want. It'll be removed in the release version so that it doesn't slow the application down.

\$\endgroup\$
1
  • \$\begingroup\$ I tried several other approaches, but so far I guess "don't do it" is looking to be the best approach. There might be some bug in the way I test it, since seeing how just using bare extension method slows the validation immensly. Up to 7 times slower just by moving the if(arg==null) throw new ArgumentExtension() to the extension method seems weird to me. Anyway, for now I think I'll accept this answer. \$\endgroup\$
    – DemoBytom
    Jan 22, 2017 at 1:24
2
\$\begingroup\$

I've yet to test this, but in theory it should perform better.

First: as stated in the other answer, you create a lot of objects immediately. Instead of doing that, you should rewrite ThrowIf to take a lambda:

public static T ThrowIf<T>(this T value, Predicate<T> test)
{
    if (test.Invoke(value))
    {
        throw new ArgumentException($"The parameter {nameof(value)} did not pass the provided {nameof(test)}.");
    }
    return value;
}

Then use it as collection.ThrowIf(x => x == null).ThrowIf(x => x.Count == 0).

You could define helper methods for it:

public static Predicate<T> IsNull<T>(this T value)
    where T : class =>
    v => v == null;

public static Predicate<T> IsEmpty<T>(this T value)
    where T : ICollection =>
    v => v.Count == 0;

Then you should be able to do:

collection.ThrowIf(collection.IsNull());

Notice how IsNull and IsEmpty don't even use value? It's just there for type-inference. With that said, we can still make a more fluid API:

public static T If<T>(this bool test, T value)
{
    if (test)
    {
        throw new ArgumentException($"The parameter {nameof(value)} did not pass the provided {nameof(test)}.");
    }
    return value;
}

public static T If<T>(this Predicate<T> test, T value)
{
    if (test.Invoke(value))
    {
        throw new ArgumentException($"The parameter {nameof(value)} did not pass the provided {nameof(test)}.");
    }
    return value;
}

public static bool IsNotNull<T>(this T value)
    where T : class =>
    value != null;

public static bool IsNotEmpty<T>(this T value)
    where T : ICollection =>
    value.Count != 0;

Then we use it as:

collection.IsNotNull().If(collection)

This also means if you want to return a different item when the Func<bool> is met you can:

var stringValue = collection.IsNotNull().If("Good work!");

Yes, it's backwards, but it should have pretty good performance overall, especially compared to your version.

It also expands the API:

var result = (items.Count > 150).If(items);
var otherResult = (x => x.Count > 150).If(items);

So you can provide lambda's, and regular bool values.

\$\endgroup\$
3
  • \$\begingroup\$ I tested those code snippets, and for me, they don't seem to provide any performance boost. There seem to be some bug with the way I test though, because I see some times go down in repeated tests, while other stay the same. I might go back and redo the thing from ground up. \$\endgroup\$
    – DemoBytom
    Jan 22, 2017 at 1:19
  • \$\begingroup\$ You need to let JIT do it's work first, then benchmark it. \$\endgroup\$ Jan 22, 2017 at 1:20
  • \$\begingroup\$ I tried running the tests in loops, call the methods by few times by themselves, adding [MethodImpl(MethodImplOptions.NoInlining)] to methods, running them from different places, in both Debug and Release. ATM I have every test method (That each test one validation method) in a for loop, that I execute 10 times. On first run all test methods perform poorly. On 2nd and consecutive some show improvement, while others don't. \$\endgroup\$
    – DemoBytom
    Jan 22, 2017 at 1:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.