Skip to main content

Building LINQ Expressions: How to run dynamic code at runtime (part 1)

How to run dynamic code at runtime (part 1)

In my previous post I talked about how to read LINQ expressions using an ExpressionVisitor, however reading isn’t the only thing you can do with expressions: you can build them at runtime!

If you’ve ever used reflection you’re probably aware that you shouldn’t use them in performance-critical situations as they’re one of the slowest parts of .NET, but what if you desperately need to use it?

Enter the world of expression-building. Before I show you how anything works, I want you to compare speeds:

          RawAccess: 00:00:00.0000344 b*1,0 (3,44E-06ms per iteration)
  ReflectionNoCache: 00:00:00.0018118 b*52,7 (0,00018118ms per iteration)
ReflectionWithCache: 00:00:00.0009518 b*27,7 (9,518E-05ms per iteration)
  ExpressionNoCache: 00:00:00.9333534 b*27132,4 (0,09333534ms per iteration)
ExpressionWithCache: 00:00:00.0000424 b*1,2 (4,24E-06ms per iteration)

Benchmark ran with .NET Core 2.2 on an AMD Ryzen 2400G @ 3.85Ghz and Windows 10.

As you can see, no methods get to the speeds of raw access, but by far the closest competitor are cached expressions.
Disclaimer: all methods benchmarked methods were ran once before starting the stopwatch, otherwise the time that the cached methods take to generate the cache on the first run make a huge impact.

Building expressions

All of the required methods to build an expression tree are contained in the Expression class.

The base expression for everything is Expression.Lambda<TDelegate>. The overload of this method that we are going to use takes in the body expression and the parameters that the lambda takes in.
The expression body can be any other expression, including a block expression.

Constant values

These are the simplest expressions, equivalent to writing e.g. 2 in C#:

Expression.Constant(2);
// 2

Constants can be of any type.

Parameters

In order to define a lambda parameter you must first create it:

ParameterExpression param = Expression.Parameter(typeof(string), "name");

You don’t need to pass in a parameter name, however it can be useful if you want to debug the generated expression later on.
Parameters also need to be passed to Expression.Lambda, and keep in mind that they must match in type and number to the lambda’s delegate type.

Operators

Binary operators allow you to do things ranging from adding two numbers to converting an object to another type.

Binary operators

All binary operators are used the same way, for example:

Expression.MakeBinary(ExpressionType.Add, Expression.Constant(1), Expression.Constant(2));
// 1 + 2

Unary operators

These can be used, for example, to convert an object to a different type:

Expression.MakeUnary(ExpressionType.Convert, Expression.Constant(2), typeof(float));
// (float)2

TIP: most binary and unary operators have shortcut functions, for example:

Expression.Add(Expression.Constant(1), Expression.Constant(2));
// 1 + 2

Expression.Convert(Expression.Constant(2), typeof(float));
// (float)2

Method calling

For this you’ll need either a MethodInfo representing the method you want to call or its name, which you can feed into Expression.Call along with the expressions for the object instance and each argument:

//Imagine a method like: void MyMethod(string word, int count);
MethodInfo m = methodof(MyClass.MyMethod);

//new MyClass().MyMethod("hello", 2 + 3)
Expression.Call(
    instance: Expression.New(typeof(MyClass)),
    method: m,
    Expression.Constant("hello"), //string word
    Expression.Add(Expression.Constant(2), Expression.Constant(3))); //int count

Here you can also see a new expression: NewExpression. It pretty much does what you think it does, it instantiates an object of the type you pass in. This overload of Expression.New selects a parameter-less constructor and throws an ArgumentException if it doesn’t find any, but you can use its other overloads to pass arguments to the constructor.

You can also use this expression builder to call static methods:

//Console.WriteLine("Hello {0}", 123)
Expression.Call(
    type: typeof(Console),
    methodName: "WriteLine",
    typeArguments: null,
    Expression.Constant("Hello {0}"),
    Expression.Convert(Expression.Constant(123), typeof(object)));

In this case I’m using the overload of Expression.Call that lets you give it a type, the method’s name and its parameter types and automatically finds and calls the appropriate method.

Member assignment and reading

First of all you need the field or property that you want to read, which can be obtained through reflection, or its name.

Accessing its value is simple, just call Expression.Property() or Expression.PropertyOrField() which will return a MemberExpression that you can use anywhere you require a value:

//Imagine a property like: int MyProperty { get; set; }
PropertyInfo prop = propertyof(MyClass.MyProperty)

var instanceParam = Expression.Parameter(typeof(MyClass));
...
Expression.Add(Expression.Property(instanceParam, prop), Expression.Constant(1));
//instanceParam.MyProperty + 1

To the property’s value you must use Expression.Assign, with the left hand expression being the same MemberExpression you used for reading its value.

Expression.Assign(Expression.Property(instanceParam, prop), Expression.Constant(42));
//instanceParam.MyProperty = 42

Block expressions

Lambdas can have block bodies with multiple lines of “C# code”, represented via a list of expressions:

var numberVar = Expression.Variable(typeof(int)); //int number;
var factorVar = Expression.Variable(typeof(int)); //int factor;
var resultVar = Expression.Variable(typeof(int)); //int result;

var expressions = new Expression[]
{
    Expression.Assign(numberVar, Expression.Constant(5)), //number = 5;
    Expression.Assign(factorVar, Expression.Constant(2)), //factor = 2;

    Expression.Assign(resultVar, Expression.Multiply(numberVar, factorVar)), //result = number * factor;

    resultVar //return result;
};

var body = Expression.Block(
    variables: new[] { numberVar, factorVar, resultVar },
    expressions: expressions);

var func = Expression.Lambda<Func<int>>(body).Compile();

Console.WriteLine(func());
//Output: 10

Each line in the code has its C# representation as a comment, but here’s what the full block would like anyways:

Func<int> func = () => {
    int number, factor, result;

    number = 5;
    factor = 2;

    result = number * factor;

    return result;
};

A couple annotations:

  • The last expression in the expressions list is interpreted as a return statement that returns the value of that expression.
  • Just like parameters, variables don’t require a name but it can be useful to assign one to them for debugging purposes.

That is all for part 1! In part 2 I will teach you about more complex expressions like if and for statements.

Comments

Popular posts from this blog

ConditionalWeakTable, what does it do?

C#'s ConditionalWeakTable, what does it do?C# has many lesser known features, some more useful than others, and one of them is the ConditionalWeakTable<TKey, TValue> (keep in mind that TKey and TValue must be reference types).
You can think of this type as a dictionary where the keys are weakly referenced, meaning that they won’t count when the GC checks if the object has to be collected. Additionally, when the keys do eventually get collected by the GC, that entry will get removed from the dictionary. This means that you can attach arbitrary objects to any object, allowing you to do something like this:publicstaticclassExtensions{privatestatic ConditionalWeakTable<object,dynamic> Table =newConditionalWeakTable<object,dynamic>();publicstaticdynamicData(thisobject obj){if(!Table.TryGetValue(obj,outvar dyn)) Table.Add(obj, dyn =newExpandoObject());return dyn;}}...var myObject ="hello"; myObject.Data().Foo ="bar"; Assert.AreEqual(myO…

C# to JS: LINQ Expressions and ExpressionVisitor

How to translate (some) C# to JavaScriptIf you’ve ever used something like EF Core you may have noticed that methods like LINQ’s Where take an Expression<Func<...>> as the first argument, instead of simply a Func<...>:
Expressions were introduced in .NET 3.5 along with the rest of LINQ and they represent the code tree that a lambda is composed of. For example, if you had a lambda like () => a + b, this is how the resulting expression tree would look like:
.NET 4.0 later introduced ExpressionVisitor, which lets you traverse the expression tree. Take this visitor for example:publicclassConsolePrinterVisitor: ExpressionVisitor {//This is just a helper method to get the Expression, normally you would already have it//so you'd just do ConsolePrinterVisitor.Visit(expression)publicvoidWriteLambda(Expression<Func<object>> expression){this.Visit(expression);}protectedoverride Expression VisitBinary(BinaryExpression node){ Console.Write("("…

Hangfire: setup and usage

How to setup Hangfire, a job scheduler for .NETInstalling HangfireSetting up HangfireSetting up storageMySQL StorageIn-memory storageRunning jobsFire-and-forget jobsDelayed jobsContinuation jobsRecurring jobsASP.NET Core job integrationConclusionHangfire is a job scheduler for .NET and .NET Core that lets you run jobs in the background. It features various different job types:Fire-and-forget jobs are executed only once, shortly after creation.Delayed jobs are very similar to fire-and-forget jobs but they wait for a specified amount of time before running.Continuation jobs, which run after their parent job has finished.Recurring jobs, perhaps the most interesting of all, run repeatedly on an interval. If you like Unix’s cron you’ll feel right at home, as this kind of job lets you specify the job interval as a cron expression. If you have never used this I recommend crontab.guru, it has a live expression editor, as well as some examples to get you started.Hangfire has two more job type…