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); } }