Jul 26 2008

Faster Than Reflection - Lambda Almighty

Category: .Net FrameworkAlexRobson @ 10:22

In my previous post Using Lambdas To Write A Faster Factory I was demonstrating a neat use of lambdas in conjunction with a dictionary to create a concise and quick factory pattern. Since then I've found some other really nice uses for lambdas and stumbled across some other LINQ related goodness. In this post, I'm going to demonstrate a way to use Lambdas in place of reflection for reading and writing values at runtime and run some performance numbers by you.

In order to do this, let's define a simple interface which describes reflection-like behavior and then we'll look at the implementation.

    public interface IDynamicallyAccessible
    {
        object ReadValue(string property);
        void WriteValue(string property, object value);
        bool CanRead(string property);
        bool CanWrite(string property);
    }

That's pretty straight forward, right? So the next step is the implementation. Let's define a really simple class with just automagic properties:

    public class Person : IDynamicallyAccessible
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public DateTime Birthdate { get; set; }
    }

At this point, you'll be getting red squigglies letting you know that Person does not implement IDynamicallyAccessible. Let's take care of that next:

    public object ReadValue(string property)
    {
        return _readers[property].Invoke(this);
    }
 
    public void WriteValue(string property, object value)
    {
        _writers[property].Invoke(this, value);
    }
 
    public bool CanRead(string property)
    {
        return _readers.ContainsKey(property);
    }
 
    public bool CanWrite(string property)
    {
        return _readers.ContainsKey(property);
    }

Ok, now that we have implemented the Methods required, we need to define the members _readers and _writers before we're done. Both are dictionaries keyed to the property name which we'll be reading or writing. The value of the dictionary for the readers will be Func<Person, object> which is a generic delegate that says "I define any function that takes a Person instance and returns an object value". The value for the writers is a little different because there's no actual return type, so we'll use Action<Person, object> because we'll be doing something to the Person instance using an object argument (lambdas which change the value of their arguments are said to have a 'side effect'). And now for the code:

    static Dictionary<string, Func<Person, object>> _readers = new Dictionary<string, Func<Person, object>>()
    {
        {"FirstName", p => p.FirstName },
        {"LastName", p => p.LastName },
        {"Age", p => p.Age },
        {"Birthdate", p => p.Birthdate}            
    };
 
    static Dictionary<string, Action<Person, object>> _writers = new Dictionary<string, Action<Person, object>>()
    {
        {"FirstName", (p,o) => p.FirstName = o.ToString()},
        {"LastName", (p,o) => p.LastName = o.ToString()},
        {"Age", (p,o) => p.Age = (int) o},
        {"Birthdate", (p,o) => p.Birthdate = (DateTime) o}
    };

Now that is a pretty cool use of lambdas. Let's put them to work and I'll throw some numbers at you so you can appreciate how much faster this is than using reflection. First the reflection way: I'm using Nvigorate for these examples (and in fact, a lot of this is in the Nvigorate.Test assembly) which means I'm using Nvigorate's Reflector class to do all the work for me. In fact, I'm using this in both sets of test cases because I added code to the read and write functions to check to see if the target instance implements IDynamicallyAccessible. If it does, then Reflector simply uses the dictionary to get the reader/writer delegate, invoke it, and bypass all the other reflection code. So, without further explanation, here are the two Test methods. Note: the method using reflection uses a different class type that does not implement IDynamicallyAccessible so that Reflector goes through the normal paces using standard reflection to write the values.

[TestMethod]
    public void ClassicReflection()
    {
        for(int i = 0; i < 100000; i++)
        {
            PlainPerson p = new PlainPerson();
            Reflector.Write(p, "FirstName", "Alex");
            Reflector.Write(p, "LastName", "Robson");
            Reflector.Write(p, "Age", 29);
            Reflector.Write(p, "Birthdate", DateTime.Parse("01/01/1979"));
        }
    }
 
    [TestMethod]
    public void LambdaReflection()
    {
        for (int i = 0; i < 100000; i++)
        {
            Person p = new Person();
            Reflector.Write(p, "FirstName", "Alex");
            Reflector.Write(p, "LastName", "Robson");
            Reflector.Write(p, "Age", 29);
            Reflector.Write(p, "Birthdate", DateTime.Parse("01/01/1979"));
        }
    }


Virtually identical, right? Remember, the difference between the two is that Person implements IDynamicallyAccessible which the Reflector class looks for before it does anything else and then uses our implementation to do the reading/writing. So, let's talk numbers. The classic reflection approach takes 9.73 seconds for 10,000 iterations while the IDynamicallyAccessible approach using Lambdas takes 1.49 seconds (a 653% speed improvement). Granted, this may not seem like a big deal to you, but think on the scale of an enterprise service bus where business objects are being hydrated from a data source and I think you'll appreciate why this makes such a big difference.

In future posts I'll demonstrate two other approaches, one tied specifically to LINQ and the other which is based more on the new 3.0 and 3.5 language features.

Tags:

blog comments powered by Disqus