Posted On: 2021-02-22
Today's post is about something of an advanced topic in C# programming: Expressions. At its simplest, an Expression in C# is a data structure for storing an executable Binary Expression Tree. Through expressions, programmers gain access to the data store underlying an algorithm, enabling architectures and designs that would otherwise be impossible*. Microsoft itself leveraged expressions in the Asp.Net MVC framework - a developer-facing framework that introduced many web developers to Expressions for the first time (myself included.)
When I described algorithms in an earlier blog post, I described them as commonly being a sequence of instructions. In many programming languages, however, those steps are first stored in another structure - one that more closely matches what the programmer originally wrote. In those languages, every instruction can be represented as an operation applied to one or two operands.
Consider, for example, the archetypal mathematical expression:
1 + 1
This is a pair of two operands (1 and 1), combined using the '+' operator. While not too fancy or interesting on its own, it
becomes much more so when trying to do something with it:x = 1 + 1
Although this might, at first, look like a more complex statement, it is, in fact, two operands joined with the '=' operator.
The first operand is a variable ('x'), while the second operand is the whole expression "1 + 1". This is all that a binary expression tree is: a binary expression, made of one operator and two operands, where each of the operands
may also be a binary expression. This is incredibly useful, as any operand at any point in the tree could be an
expression, allowing for any amount of nesting.
When visualized, the tree looks something like this:
The instruction "x = 1 + 1" can be expressed as a heirarchical tree.
As mentioned earlier, the Expression type in C# is a data structure designed to represent a binary expression tree. Expression objects can't be created directly (the Expression type is an abstract base class), but there are several helpers and factories that can be used to create said objects. For example, our old "1 + 1" example could be created:
Expression.MakeBinary(ExpressionType.Add, Expression.Constant(1), Expression.Constant(1))
As you can imagine, building a large tree like this can be quite cumbersome. Fortunately, lambdas provide for a much easier way to build expressions.
LambdaExpression onePlusOneExpression = () => 1 + 1;
This can be particularly useful when you're looking to take in expressions as parameters, since lambdas are far more approachable for most developers:void ExecuteAndLog<T>(Expression<Func<T>> requestedActivity){
Accepting lambda expressions as parameters is quite common - if you've ever used Linq to SQL or Asp.Net MVC's Html Helpers, then you've been been calling methods that do exactly that. In light of that, I will be focusing more on this next week, when I'll explain more about how to actually use Expressions. I hope you'll join me then!