Developer

Code concepts: LINQ's expression trees

In this overview about LINQ's expression trees, Justin James shows how you might use expression objects in your projects.

 

One of the core components that underlies .NET Language-Integrated Query (LINQ) are expression trees. LINQ statements operate on types of IEnumerable<T> or IQueryable<T>; in the latter case, the statement is translated into a format that an external system can understand, run against the external system, and translated into LINQ results. This is where the expression trees come into play. The LINQ statement is examined and turned into a type of expression object that presents the code as data that can be inspected; from there, the LINQ provider is able to look at the expression, determine how to execute the request, and then provide results. In this column, I look at what an expression tree is, and how you might use it in your projects.

To say that an expression tree "presents the code as data that can be inspected" means that you create can expression object and each of its properties represents code or information about code. For example, a BinaryExpression object represents an expression that has a property for the "left side" parameter and the "right side" parameter, which can be examined for things such as the type of the parameter and the name. As a result, you can "drill down" through an expression's properties (with a normal recursive tree walking algorithm) and look at every part of the expression at the lowest level.

Expression objects can be created from existing code or manually composed; the latter case is where a lot of the objects' utility comes into play. By writing code that creates expressions based on runtime conditions, you can do all sorts of tricks, including making a domain-specific language with its own syntax (an example that I am building towards in a series of articles). An expression object is not just a lifeless collection of data, though; it can be compiled at runtime to present an actual function that can be run. While expressions are immutable, there is no reason why you cannot take an existing expression and copy its parts into a new one and make needed changes along the way.

Here's a simple application that creates an expression at runtime and consumes it:

using System;

using System.Linq.Expressions;

namespace TestProject

{

class Program

{

public static Func<int, int> CreateAdderFunction(int amountToAdd)

{

var numberToAdd = Expression.Parameter(typeof(int), "number");

var addAmount = Expression.Constant(amountToAdd, typeof(int));

var binaryExpression = Expression.Add(numberToAdd, addAmount);

var lambda = Expression.Lambda<Func<int, int>>(binaryExpression, new ParameterExpression[] { numberToAdd });

return lambda.Compile();

}

static void Main(string[] args)

{

var addAmount = int.Parse(args[0].ToString());

var argument = int.Parse(args[1].ToString());

Console.WriteLine(CreateAdderFunction(addAmount).Invoke(argument));

Console.ReadLine();

}

}

}

In this application, we have a method (CreateAdderFunction) that returns a Func<int, int> object, a function delegate that accepts an int as an input parameter and returns an int as its output. It takes a parameter of type int, amountToAdd. Inside this function, it creates a ParameterExpression object called numberToAdd; this object represents a parameter of type int named "number." Next, we define a ConstantExpression object (addAmount) of type int and assign it the value amountToAdd (the parameter passed to the method). Then a BinaryExpression object with a node type of Add is created; the left side of this object is the numberToAdd ParameterExpression, and the right side is the addAmount ConstantExpression. After this, a LambdaExpression is made, with the signature of Func<int, int>, and it is passed the BinaryExpression and an array of ParameterExpression objects, which is initialized to contain our numberToAdd variable. Finally, we compile the LambdaExpression and return it, which gives us the Func<int, int> that we promised to provide to the caller.

This is a very roundabout way of constructing the equivalent of the following:

public static int Adder(int number)

{

const int numberToAdd = 5;

return numberToAdd + number;

}

But, in the case of our expression version, the value of "numberToAdd" is determined at runtime and within code. This is a very simple example, but it illustrates the point well. With an expression, we could create new functionality at runtime. Granted, this has been in .NET for some time with things like System.Reflection.Emit, but expressions are easier to work with than previous runtime function creations. Also, you can see from how we composed the function how it could be useful to read the data of an expression built from an existing function. Code can be made to analyze an expression and take action based on what it finds.

Expressions trees are a pretty advanced topic, and you won't find a use for them in every project. I hope this overview whets your interest in expression trees and introduces you to a technique that you may want to learn more about. I may use expression trees in my RPN calculator project, so stay tuned for more on this topic.

J.Ja

Disclosure of Justin's industry affiliations: Justin James has a contract with Spiceworks to write product buying guides; he has a contract with OpenAmplify, which is owned by Hapax, to write a series of blogs, tutorials, and articles; and he has a contract with OutSystems to write articles, sample code, etc.

———————————————————————————————————————————-

Get weekly development tips in your inbox Keep your developer skills sharp by signing up for TechRepublic's free Web Developer newsletter, delivered each Tuesday. Automatically subscribe today!

About Justin James

Justin James is the Lead Architect for Conigent.

Editor's Picks

Free Newsletters, In your Inbox