🌴How to Convert Any Object to a Dictionary Using Expression Trees in C#

14 minutes

In the previous article we explained Expression Trees, what they are and how to use them. In this article we will utilize them to solve a real-world problem that usually comes up in applications where object inspection is required.

🤔The problem

There are occasions in your applications where you wish you could treat an object as a Dictionary of its properties. Imagine a simple POCO object like this:

C#
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsActive { get; set; }
    public int ShoeSize { get; set; }
}

Sometimes we simply need to iterate over its properties and retrieve their values. To put it another way, to map each property to its corresponding value. For the class above, such a representation would look like this:

C#
new Dictionary<string, object>()
{
  { "Name", "Alice" },
  { "Age", 30},
  { "ShoeSize", 39},
  { "IsActive", true},
};

There are many ways we can do this. Some of them are:

  • By manually adding each property and its value to a dictionary.
  • By using Reflection and iterating over the properties.
  • By using Expression Trees.

The solution that we will come up with, needs to be efficient, reusable and extensible.

Ideas

First, let’s start by manually adding each property to a dictionary. Like this:

C#
public static Dictionary<string, object> PersonToDictionary(Person person)
{
    return new Dictionary<string, object>()
    {
        { "Name", person.Name },
        { "Age", person.Age},
        { "ShoeSize", person.ShoeSize},
        { "IsActive", person.IsActive},
    };
}

Although this solution is fast, extensible and clean, unfortunately it’s not reusable. We need to manually write similar methods for each different type we encounter. On top of that, we have to remember to update this method whenever we add or remove a property from our Person class.

In order for this solution to be reusable for many types, we have to dynamically initialize the Dictionary with the object’s properties. We could use Reflection to get the properties (and their values) of an object at runtime. Here is the code:

C#
public static Dictionary<string, object> ObjectToDictionary<T>(T obj)
{
    var dictionary = new Dictionary<string, object>();
    Type type = typeof(T);

    foreach (PropertyInfo property in type.GetProperties())
    {
        dictionary.Add(property.Name, property.GetValue(obj));
    }

    return dictionary;
}

Now, this is reusable but it’s not efficient. Reflection is notorious for being slow. Furthermore, we have to call this method for every instance of an object we would like to convert. Even worse, this solution becomes inefficient in a situation where we have to run this method in performance critical code.

Using Expression Trees

By using Expression Trees, we can take advantage of the benefits of both solutions while at the same time eliminating their drawbacks.

🎯The goal

Expression Trees allow us to generate and compile the PersonToDictionary method at runtime. Additionally, we can configure them to generate this method for any type, not just Person. More specifically, the generated method will initialize and return a dictionary with all the properties of T in the same way PersonToDictionary does for the type Person.

The method that will build the Expression Tree will be called CreateMapperMethodFor<T>. Furthermore, it will compile the expression tree into executable code and return an actual delegate to it. At that point, we will have an “in-memory” version of PersonToDictionary but specifically for the type T as if we had written it by hand.

We will refer to the generated method as “ObjectToDictionary“, because as we just discussed, it is no longer specifically about Person type. As a side note, the generated method is stored in RAM and managed by the .NET runtime. It is only available to us through the delegate.

Here is an example of how we will use the CreateMapperMethodFor<T> method:

C#
// Phase 3
Func<Person, Dictionary<string, object>> personToDictionaryMethod = 
  CreateMapperMethodFor<Person>();
  
var person = new Person { ... }; 
Dictionary<string, object> dictionary = personToDictionaryMethod.Invoke(person);

How it works

Before we move on let’s clarify a few things. When it comes to Expression Trees it is useful to think in terms of 3 phases.

  1. Phase 1 is the compile time of the CreateMapperMethodFor<T> method. In other words, when we build the expression tree or even more specifically, when we build our application.
  2. Phase 2 is the compile time of the expression tree. This is when the expression tree we’ve written is compiled into executable code. It’s easy to see why this phase can cause confusion. Phase 1 is the compile time of the code we have written but Phase 2 is the compile time of the code the expression tree has written. In other words, Phase 1 happens when we compile our entire application into an executable exe or dll file. Phase 2 happens when the .NET runtime compiles the expression tree into executable code. Very soon we will see how we will trigger the compilation of the expression tree.
  3. Phase 3 is when we call our generated method.

As we saw, on Phase 1 CreateMapperMethodFor<T> builds the expression tree for the dictionary initialization with all the properties of that type. On Phase 2 the expression tree is compiled into executable code and generates the ObjectToDictionary method for that particular type T. This means, that the generated method knows exactly the type it for which it initializes the dictionary. T doesn’t exist anymore; its place has been taken by the concrete type it used to represent on Phase 1.

The delegate that CreateMapperMethodFor<T> returns, is referencing the ObjectToDictionary for the concrete type of T. And on Phase 3, we can use it like any other delegate to call the generated method. From this point onwards, every time we call the delegate, we execute the specific version of ObjectToDictionary as if it was any other method.

Once we have generated the ObjectToDictionary method for a type T, we don’t have to regenerate it again for the same type. The generated method includes the dictionary initialization for the concrete type of T. All we have to do is pass an object of that type to the generated method. The performance of the generated method will be almost identical to the manually written dictionary initialization for the concrete T.

As a reminder, here is the PersonToDictionary method from before:

C#
public static Dictionary<string, object> PersonToDictionary(Person person)
{
    return new Dictionary<string, object>()
    {
        { "Name", person.Name },
        { "Age", person.Age},
        { "ShoeSize", person.ShoeSize},
        { "IsActive", person.IsActive},
    };
}

To generate this method, we need to put together the instructions that describe how the dictionary will be initialised. We will build an “in-memory” representation of the dictionary initialisation using the Expression Tree API. We will then compile this tree into an executable method. The delegate that we get by calling Lambda.Compile is pointing to this in-memory executable code.

Let’s build the solution

Now that we know what we want to build, let’s break it down and explore step by step how to make it happen. Later, we will see the complete solution.

Step 1 – Identify the input object and type

The first thing our generated method needs is an input object. It must accept a parameter that represents this object. We name it input and its name will be in the scope of the generated method. As we will see later, we will use this parameter to access the properties of the input object in our generated method.

The input object will have a type; however, we don’t know this type when we build the expression tree. It will be provided by whoever builds the ObjectToDictionary method for that particular type (line 3 of this example). The type of the input object is the only piece of information we need to know in Phase 1. The actual input object will be provided to the generated method at runtime as we saw here on Phase 3.

In expression trees, method parameters are represented by a ParameterExpression object. Here is what we have so far:

C#
Type type = typeof(T);
ParameterExpression param = Expression.Parameter(type, "input");
...

Step 2 – Locate the Add method

Using a constructor to initialise a dictionary with keys and values is similar to declaring a dictionary and calling the Add method for each key/value pair. Similar to this:

C#
var person = new Dictionary<string, object>();

person.Add("Name", "Alice");
person.Add("Age", 30);
person.Add("ShoeSize", 39);
person.Add("IsActive", true);

The Expression Tree API uses Reflection to identify the method it needs to call on the initialisation of each key/value pair. For this purpose, we can use the MethodInfo class to access the Add method of our dictionary. Later, we will build the expression tree that calls this method for each property/value pair of our type.

C#
MethodInfo addMethod = typeof(Dictionary<string, object>).GetMethod("Add")!;

Step 3 – Loop over the properties

We now have, the input object, its type and the method to initialize each element of the dictionary. The next step is to use that method to initialise each element with the property name/value pair. To do that, we will iterate over the properties of the type T using the Reflection type.GetProperties() method. Then, for every property we will generate a statement that adds its property name/value pair to our dictionary. In summary, this is the statement we want to generate:

C#
dictionary.Add(property.Name, property.GetValue(obj));

Unfortunately, the Expression Trees API is a bit too verbose and in order to generate this statement we have to write the following loop:

C#
var elementInits = new List<ElementInit>();
foreach (PropertyInfo property in type.GetProperties())
{
  ElementInit initializer = Expression.ElementInit(
      addMethod,
      Expression.Constant(property.Name),
      Expression.Convert(Expression.Property(param, property), typeof(object)));
  
  elementInits.Add(initializer);
}

This might look overwhelming so let’s break it down

  • The Expression.ElementInit is a factory method that returns an ElementInit object with the parameters we will specify.
  • The first parameter is the MethodInfo object that must be used to initialise the element in the dictionary. The “Add” method we saw earlier.
  • The second parameter is the name of the property which will be used as the key to the dictionary element. For example, the literal value “Name”
  • The third parameter is the value of the property converted to an object. The call to Expression.Convert generates a cast similar to (object)obj. This will be used as the value of dictionary element.
  • Finally, we store all these ElementInit objects in a list to use them in the next step.

Step 4 – Initialise the dictionary

After we add all the properties in the dictionary, we have to build the collection initialization expression which is represented by the ListInitExpression class. We do that by calling the Expression.ListInit method. This method accepts the type of the collection we want to initialise plus a list with all the element initialisation expressions which we built in the previous step. Like so:

C#
ListInitExpression dictionaryInitializer = Expression.ListInit(
  Expression.New(typeof(Dictionary<string, object>)), elementInits);

Step 5 – Build the lambda expression object

Now that we have the dictionary initializer in an expression, we can build a lambda expression with it. The lambda expression is the container of our expression tree or in other words, the statement that wraps the expression tree into a method. For this reason, we need to specify the parameter(s) of this method. In this case, the param parameter we created on Step 1.

C#
var lambda = 
  Expression.Lambda<Func<T, Dictionary<string, object>>>(dictionaryInitializer, param);

Step 6 – Compile the lambda into a delegate

Finally, we can compile the whole expression tree into an executable method and get a delegate to it:

C#
Func<T, Dictionary<string, object>> compiled = lambda.Compile();

We can now use this delegate as any other delegate to call the generated ObjectToDictionary method for the particular type T.

🏅The final solution

Here is the final method:

C#
public static Func<T, Dictionary<string, object>> CreateMapperMethodFor<T>()
{
  Type type = typeof(T);
  ParameterExpression param = Expression.Parameter(type, "input");

  MethodInfo addMethod = typeof(Dictionary<string, object>).GetMethod("Add")!;

  var elementInits = new List<ElementInit>();
  foreach (PropertyInfo property in type.GetProperties())
  {
    ElementInit initializer = Expression.ElementInit(
      addMethod,
      Expression.Constant(property.Name),
      Expression.Convert(Expression.Property(param, property), typeof(object)));

    elementInits.Add(initializer);
  }

  ListInitExpression dictionaryInitializer = Expression.ListInit(
    Expression.New(typeof(Dictionary<string, object>)), elementInits);

  var lambda = Expression.Lambda<Func<T, Dictionary<string, object>>>(dictionaryInitializer, param);

  // Phase 2:
  Func<T, Dictionary<string, object>> compiled = lambda.Compile();

  return compiled;
}

And here is the full code of how we could use it:

C#
public static void Main(string[] args)
{
  // Phase 1:
  Func<Person, Dictionary<string, object>> mapper = CreateMapperMethodFor<Person>();

  var person1 = new Person
  {
    Name = "Alice",
    Age = 30,
    IsActive = true,
    ShoeSize = 39
  };

  var person2 = new Person
  {
    Name = "Bob",
    Age = 28,
    IsActive = true,
    ShoeSize = 44
  };

  Console.WriteLine("Expression Trees solution");
  Console.WriteLine("-------------------------");
  Console.WriteLine();

  // Phase 3:
  Dictionary<string, object> person1Dictionary = mapper.Invoke(person1);
  Dictionary<string, object> person2Dictionary = mapper.Invoke(person2);

  Console.WriteLine("Person 1 Dictionary:");

  foreach (var kvp in person1Dictionary)
  {
    Console.WriteLine(kvp);
  }

  Console.WriteLine();
  Console.WriteLine("Person 2 Dictionary:");

  foreach (var kvp in person2Dictionary)
  {
    Console.WriteLine(kvp);
  }

  Console.WriteLine();
}

As we can see in this code, the CreateMapperMethodFor<Person>() is called only once at line 4. Once we have generated the mapper method, we can use call it with different objects of type Person as in lines 27 and 28.

🏗️Ways to improve

The solution I presented is far from perfect and could definitely benefit from certain improvements (which I shall leave as an exercise for the reader) such as:

  • Support an option to ignore properties that have a null value.
  • Support an option to initialize a case insensitive dictionary. Meaning, that accessing the keys “Age” and “age” should refer to the same property.
  • Add type information about the property’s value.
  • Support for nested objects.
  • Support for generic objects, for example accessing the properties of an object like List<int>

🏁Final Thoughts

Through my work with Expression Trees, I found that the most challenging part is distinguishing between the expression construction and the delegate execution phases. The real difficulty lies in understanding that you’re writing a program that will generate another program. And that the program that you are writing should compile and execute the program that your program produces. It’s probably best to think of it (as extraordinary as it sounds) as program writing another program.

However, this is what makes Expression Trees so powerful. The fact that they represent code as a live data structure that it can then be configured, compiled, and executed. It allows the programmer to produce code based on decisions and variants that will be decided at runtime. This opens the door to a very exciting field of computer programming called Metaprogramming.

In this post, I did my best to simplify this complex topic without sacrificing accuracy and clarity. Feel free to reach out with any feedback

Happy Coding!

Thanos


Related Articles:

🌳 C# Expression Trees: Intro, Construct, Execute, Analyze