Picture by Isabelle Portes

Fast Conversion of Objects to Dictionaries in C#

Converting (plain old) C# objects to dictionaries is easy with Reflection (e.g., as seen here). But Reflection is rather slow, so here’s an approach using precompiled Linq Expressions that speeds things up a little.

To describe what the code should do, here’s a little unit test:

using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MunirHusseini;

namespace UnitTestProject1
{
    [TestClass]
    public class ObjectToDictionaryTest
    {
        [TestMethod]
        public void GeneratesDictionary()
        {
            var a = new
            {
                key1 = "value1",
                key2 = "value2",
                key3 = "value3",
                key4 = "value4",
                key5 = "value5"
            };

            // The extension method ToDictionary() is what we're testing here.
            var d1 = a.ToDictionary();
            var d2 = new Dictionary<string, object>
            {
                ["key1"] = "value1",
                ["key2"] = "value2",
                ["key3"] = "value3",
                ["key4"] = "value4",
                ["key5"] = "value5"
            };

            Assert.IsTrue(d1.SequenceEqual(d2));
        }
    }
}

Before showing the actual code, here’s a little speed test to show the speed-up in comparison to pure Reflection.

static void Main(string[] args)
{
    var a = new
    {
        key1 = "value1",
        key2 = "value2",
        key3 = "value3",
        key4 = "value4",
        key5 = "value5"
    };

    var n = 1000000;
    var sw = new Stopwatch();
    var h = 0;
    for (var i = 0; i < n; i++)
    {
        sw.Start();
        var dict = a.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(a, null));
        sw.Stop();
        h = $"{h}{dict.GetHashCode()}".GetHashCode();
    }
    Console.WriteLine($"Just to prevent compiler optimizations to optimize away our code: {h.GetHashCode()}.");
    Console.WriteLine($"Reflection-based: Time for {n} iterations: {sw.ElapsedMilliseconds} ms.");

    sw = new Stopwatch();
    sw.Start();
    var dict2 = a.ToDictionary();
    sw.Stop();
            
    Console.WriteLine($"Just to prevent compiler optimizations to optimize away our code: {dict2.GetHashCode()}.");
    Console.WriteLine($"Time for first expression-based conversion: {sw.ElapsedMilliseconds} ms.");

    sw = new Stopwatch();
    for (var i = 0; i < n; i++)
    {
        sw.Start();
        var dict = a.ToDictionary();
        sw.Stop();

        h = $"{h}{dict.GetHashCode()}".GetHashCode();
    }
    Console.WriteLine($"Just to prevent compiler optimizations to optimize away our code: {h.GetHashCode()}.");
    Console.WriteLine($"Precompile expression-based: Time for {n} iterations: {sw.ElapsedMilliseconds} ms.");

    Console.Read();
}

The code above generates the following output:

Just to prevent compiler optimizations to to optimize away our code: 1634877345.
Reflection-based: Time for 1000000 iterations: 1605 ms.

Just to prevent compiler optimizations to to optimize away our code: 15852404.
Time for first expression-based conversion: 35 ms.

Just to prevent compiler optimizations to to optimize away our code: 741981076.
Precompile expression-based: Time for 1000000 iterations: 528 ms.

We can see that the pre-compiled approach is 3 times faster than the Reflection-based approach. So now, without further ado, here’s the actual code. Enjoy.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace MunirHusseini
{
    public static class PocoToDictionary
    {
        private static readonly MethodInfo AddToDicitonaryMethod = typeof(IDictionary<string, object>).GetMethod("Add");
        private static readonly ConcurrentDictionary<Type, Func<object, IDictionary<string, object>>> Converters = new ConcurrentDictionary<Type, Func<object, IDictionary<string, object>>>();
        private static readonly ConstructorInfo DictionaryConstructor = typeof(Dictionary<string, object>).GetConstructors().FirstOrDefault(c => c.IsPublic && !c.GetParameters().Any());

        public static IDictionary<string, object> ToDictionary(this object obj) => obj == null ? null : Converters.GetOrAdd(obj.GetType(), o =>
        {
            var outputType = typeof(IDictionary<string, object>);
            var inputType = obj.GetType();
            var inputExpression = Expression.Parameter(typeof(object), "input");
            var typedInputExpression = Expression.Convert(inputExpression, inputType);
            var outputVariable = Expression.Variable(outputType, "output");
            var returnTarget = Expression.Label(outputType);
            var body = new List<Expression>
            {
                Expression.Assign(outputVariable, Expression.New(DictionaryConstructor))
            };
            body.AddRange(
                from prop in inputType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)
                where prop.CanRead && (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(string))
                let getExpression = Expression.Property(typedInputExpression, prop.GetMethod)
                select Expression.Call(outputVariable, AddToDicitonaryMethod, Expression.Constant(prop.Name), getExpression));
            body.Add(Expression.Return(returnTarget, outputVariable));
            body.Add(Expression.Label(returnTarget, Expression.Constant(null, outputType)));

            var lambdaExpression = Expression.Lambda<Func<object, IDictionary<string, object>>>(
                Expression.Block(new[] { outputVariable }, body),
                inputExpression);

            return lambdaExpression.Compile();
        })(obj);
    }
}

Freelance full-stack .NET and JS developer and architect. Located near Cologne, Germany.

Leave a Reply

Your email address will not be published. Required fields are marked *