Skip to content

C#: Efficient retrieval of Expression values

Some days ago I’ve had an interesting technical discussion with a colleague of mine regarding the possibility of implementing Guard classes with Expressions in C#. Such a class enables easy argument checking in form of (technical) preconditions on method entry. Thereby the checking logic is encapsulated in the Guard class.

One method of such a Guard class could be AssertNotNull() which checks, if a given method argument equals null and if true throws an ArgumentNullException. By the use of Expressions the parameter to check can be given to AssertNotNull() in a very elegant way:

public void MyMethod(SomeType elem)
{
    Guard.AssertNotNull(() => elem);

    // actual method logic
}

Through evaluation of the Expression the AssertNotNull() method is able to extract both the name and value of the argument. Thus there’s no necessity to provide both as parameters to AssertNotNull(). Moreover you do not need to provide the argument name as string.

Value by compilation

It’s very easy to extract the argument value through compilation and invocation of the Expression. Hence this solution is often taken by developers who work with Expressions in this way. The following snippet shows this implementation:

public static class Guard
{
    public static void AssertNotNull<T>(Expression<Func<T>> selector)
    {
        T value = selector.Compile().Invoke();
        if (value == null)
        {
            string name = ((MemberExpression)selector.Body).Member.Name;
            throw new ArgumentNullException(name);
        }
    }
}

Value by evaluation

In the conversation with my colleague we caught the point that the extraction of the argument value from an Expression is even possible without performing the costly compilation. This is enabled by converting the Expression into a ConstantExpression, from which we can catch the value by executing GetValue() on a FieldInfo instance:

public static class Guard
{
    public static void AssertNotNull<T>(Expression<Func<T>> selector)
    {
        var memberSelector = (MemberExpression)selector.Body;
        var constantSelector = (ConstantExpression)memberSelector.Expression;
        object value = ((FieldInfo)memberSelector.Member)
            .GetValue(constantSelector.Value);

        if (value == null)
        {
            string name = ((MemberExpression)selector.Body).Member.Name;
            throw new ArgumentNullException(name);
        }
    }
}

This doesn’t look very spectacular on first sight. The code is more complicated and it’s necessary to provide a variable in the Expression. Properties and data hierarchies don’t work with this solution, but this shouldn’t be a problem when checking argument values against null. Of course with a more sophisticated solution (iterative/recursive evaluation of the Expression tree) you can enable such scenarios, but this is out of scope of this blog post…

Looking at the execution times

So what’s the point of the second solution? It’s simple: compilation is much more time-intensive! In a little runtime test I’ve compared both solutions. A call of AssertNotNull() with a valid argument (!= null) over 10.000 iterations resulted in the following execution times:

Please note the immensive time difference! The compiled solution takes more than 53 seconds, while the evaluation takes less than 0,35 seconds. This makes a time factor of more than 156, by which the evaluated solution is faster than the compiled approach.

Conclusion

When handling with Expressions you should take the execution time into account! Once a method is executed inside of a loop and you follow defensive programming and check method parameters at every place, an argument check can become an important time factor. When possible you should favor evaluation over compilation of Expressions.

kick it on DotNetKicks.com

{ 1 } Comments

  1. Keith J. Farmer | 2010/10/28 at 19:38 | Permalink

    Have you considered caching the compilation results?

Post a Comment

Your email is never published nor shared. Required fields are marked *