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.
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) let convertExpression = Expression.Convert(getExpression, typeof(object)) select Expression.Call(outputVariable, AddToDicitonaryMethod, Expression.Constant(prop.Name), convertExpression)); 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); }
Sadly, this only works for POCOs with string-typed properties. If the objects to be converted contains primitive-typed properties (like int or bool) this fails. From what I see, the problem is trying to add an int (or bool) value to Dictionary. The values must be boxed into an object first, which your expression is not doing.
Thank you, Adre, for pointing this out. Oh boy, all the dissapointed readers…
Anyway, I updated the code to add a ConvertExpression to explicitly convert the property value to object.
Again, thank you very much.