One of the recurring themes on this blog is the idea of making the computer work harder, not the user or the developer. I discovered recently that there was an easier and faster reflection technique than the one I was using - the technique that I’d been using for most of a decade!

Here’s an example of the slow way that anyone getting started with .NET Reflection would naturally write:

public string ReadNameNaive()
{
    var property = typeof(Container).GetProperty(
        "Name",
        BindingFlags.Instance
        | BindingFlags.GetProperty
        | BindingFlags.Public 
        | BindingFlags.NonPublic);
    return (string)property.GetValue(_container);
}

This works - but it’s hardly very performant. Let’s look at how to make it better.

The value for the property variable doesn’t change from call to call, so it makes sense to cache the value once:

private static readonly PropertyInfo _property =
    typeof(Container).GetProperty(
        "Name",
        BindingFlags.Instance
        | BindingFlags.GetProperty
        | BindingFlags.Public
        | BindingFlags.NonPublic);

public string ReadNameCached()
{
    return (string)_property.GetValue(_container);
}

Comparing the performance of the two (using BenchmarkDotNet), we get the following:

MethodMeanErrorStdDev
ReadNameNaive268.629 ns5.4141 ns6.4450 ns
ReadNameCached186.047 ns3.6928 ns4.6702 ns

The cached version is substantially faster - but we can do even better.

We can use the .NET Expressions framework, part of LINQ, to dynamically compile a lambda expression to do the read. The code is a little complex - but it just takes a bit of trial and error to get right:

private static readonly Func<Container, string> _nameReader
    = GetNameReader();

private static Func<Container, string> GetNameReader()
{
    var property = typeof(Container).GetProperty(
        "Name",
        BindingFlags.Instance
        | BindingFlags.GetProperty
        | BindingFlags.Public
        | BindingFlags.NonPublic);
    var propertyReader = property.GetMethod;
    var containerParameter = Expression.Parameter(typeof(Container));
    var readPropertyCall = Expression.Call(
        containerParameter, propertyReader);
    var castToString = Expression.Convert(readPropertyCall, typeof(string));
    var lambda = Expression.Lambda<Func<Container, string>>(
        castToString, containerParameter);
    return lambda.Compile();
}

public string ReadNameExpression()
{
    return _nameReader(_container);
}

It’s worth noting that there is a substantial up-front initialization cost caused by the compilation, but it soon pays off. I’ve written code like this many times, and the performance is spectacularly better than the other options - 20x faster:

MethodMeanErrorStdDev
ReadNameNaive268.629 ns5.4141 ns6.4450 ns
ReadNameCached186.047 ns3.6928 ns4.6702 ns
ReadNameExpression9.120 ns0.2378 ns0.2224 ns

What I learnt recently was that there’s a much easier way to achieve the same result:

private static readonly Func<Container, string> _nameReader
    = GetNameReader();

private static Func<Container, string> GetNameReader()
{
    var property = typeof(Container).GetProperty(
        "Name",
        BindingFlags.Instance
        | BindingFlags.GetProperty
        | BindingFlags.Public
        | BindingFlags.NonPublic);
    var propertyReader = property.GetMethod;
    return (Func<Container, string>)
        propertyReader.CreateDelegate(typeof(Func<Container, string>));
}

public string ReadNameDelegate()
{
    return _nameReader(_container);
}

Very simple and elegant. Straightforward to write and easy to read. But how well does it work?

MethodMeanErrorStdDev
ReadNameNaive268.629 ns5.4141 ns6.4450 ns
ReadNameCached186.047 ns3.6928 ns4.6702 ns
ReadNameExpression9.120 ns0.2378 ns0.2224 ns
ReadNameDelegate2.460 ns0.1217 ns0.1138 ns

That’s better than 100x faster performance than the original and nearly 4x faster than the technique I thought was the fastest possible. Nice.

Updated Monday 15th October: Modified code examples to be more explicit.

Comments

blog comments powered by Disqus
Next Post
Introducing the Priority Queue  20 Oct 2018
Prior Post
Avoiding Magic Strings  06 Oct 2018
Related Posts
Browsers and WSL  31 Mar 2024
Factory methods and functions  05 Mar 2023
Using Constructors  27 Feb 2023
An Inconvenient API  18 Feb 2023
Method Archetypes  11 Sep 2022
A bash puzzle, solved  02 Jul 2022
A bash puzzle  25 Jun 2022
Improve your troubleshooting by aggregating errors  11 Jun 2022
Improve your troubleshooting by wrapping errors  28 May 2022
Keep your promises  14 May 2022
Archives
October 2018
2018