There are times where I needed to do some things dynamically and I typically think of Expression Trees to do it. Don't get me wrong, Reflection does the job most of the time, but it's known to be very slow in almost all cases. I happen to have a bit of experience in the former.
This blog post is the start of a series about Expression Trees and some use cases I found myself needing them. If you're interested to know more about this topic, then stay tuned!
What are Expression Trees
Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y.
You can compile and run code represented by expression trees. This enables dynamic modification of executable code, the execution of LINQ queries in various databases, and the creation of dynamic queries.
They can be used to solve a lot of problems where you need to do things dynamically or some things are only available at runtime.
- They are way more performant than Reflection. As with manually written code, expression trees generate IL which gets optimized further along the way (JIT).
- They can do a lot more than Reflection.
- They are easy to understand and create due to their nature (trees).
Imagine we have the following class:
We would like to extract the following:
Basically, we would like to extract all the properties of a class and their values. For my case, I needed to create query strings (e.g.
categoryId=qgq65s1d6qs5d1g&page=1&size=10&filter=price>10) based on request classes. It can be done manually for each class, but I'm too lazy.
In this section, I'll explain how I personally solve these kind of problems using Expression Trees and how exactly I create the trees.
Firstly, we'll look at the manual approach:
Standard property getter. Now let's imagine that, for some reason, the type of
object. We can't access
CategoryId directly anymore, but since we know the actual type, we can do the following:
Simple enough, right?
Now you want to create a function out of this in order to get the full picture:
Writing what we want as a lambda expression helps us picture how our compiled Expression Tree will look like. In all of these examples, we assumed that
instance is always of type
ListProductsRequest. But in reality, it's not, and this is where dynamic code comes into play.
Expression<Func<object, object>>and debug the expression directly. It will give you a better idea of how the tree is constructed, but it won't help with the unkown type problem.
If we represent the last lambda expression as a tree, it'll look like this:
instance is the parameter that we want to retrieve the values of the properties from.
- Cast the instance object to the correct type (e.g.
instance as ListCustomersRequest).
- Retrieve the value of the property (e.g.
(instance as ListCustomersRequest).Page).
- Convert the value to
(object)((instance as ListCustomersRequest).Page)).
Of course, the whole Expression Tree is then compiled into a
You might be wondering by now, where exactly do we specify what property do we want?
We will be embedding it inside the Expression Tree, so that we don't have to add an extra parameter to our function and looking for the property every time it's called.
Now for the fun part! Let's create the Expression Tree:
As you can see, the method takes a
PropertyInfo as an argument and returns a single getter.
An example usage would be:
Of course, this needs to run only once per
Type and be cached in memory.
Just to get an idea of how better Expression Trees are from other solutions, I set up a quick & simple benchmark that tests 3 ways to get a property's value and calculate the total time taken on 1 000 000 calls (in ms).
- Store a compiled getter lambda created with our previous code.
As you can see, Expression Trees outperform Reflection by far.
Running code dynamically can be done in a lot of ways, today we saw how we can do it using Expression Trees and how performant it is over plain Reflection. While you probably won't need to dynamically run code a lot in your developer journey, I believe it's something worth learning and it's a lot of fun!
I have a couple of use cases more that I'll be sharing in future posts.