Introduction
A lambda expression is an unnamed method that with minimal syntax you can use to create either
- A delegate instance. As we know a delegate is a type that safely encapsulates a method, similar to a function pointer in C and C++. It is very broadly used in C#.
- An expression tree, of type
Expression<TDelegate>
. A tree represents written code inside the lambda expression itself. This allows the lambda expression to be analyzed and interpreted during the runtime.
In following paragraphs we'll try to explain differences between these two choices and explain how we can build delegate instance or expression tree from lambda expression.
Lambda expressions as delegates
Lambda expressions can be understood as an evolution of anonymous methods. They are actually a superset of anonymous methods, so all restrictions that apply to anonymous methods also apply to lambda expressions. Huge benefit is they are almost always more readable and shorter. Basically, you can do (almost) everything in an lambda expression that you can do in a normal method body. But to name few restrictions
- You can't use
break
, goto
or continue
to jump from the lambda expression scope.
- No unsafe code can be executed inside a lambda expression.
- You cannot use contravariance. You have to specify the parameter types.
The parameters of an expression can be explicitly or implicitly typed. But the return type of the lambda must be implicitly convertible to the return type of the delegate. Lambda expressions have a lot of shortcuts and that will make code very compact. They have special conversion rules.
Shortly let's have a look how lambda expressions are translated by compiler in compile time. These lines basically represents which work compiler does step by step.
Func<string, int> f = text => text.Length;
Func<string, int> f = (text) => text.Length;
Func<string, int> f = (string text) => text.Length;
Func<string, int> f = (string text) => { return text.Length; };
Func<string, int> f = delegate (string text) { return text.Length; };
We should point out once again that these steps are happening during compilation (not run) time. You can understand it like syntactic sugar for readable and compact code.
We can think that work for compiler finished here. But it is not right. Compiler is still generating IL code. But the compiler will create method within the existing class. Then this method is used when it creates the delegate instance - just as if it were a normal method.
So basically this simple code
namespace LambdaExpressions
{
class Program
{
static void Main(string[] args)
{
Func<string, int> f = text => text.Length;
}
}
}
Is transformed in this IL form
These methods have names not valid in C#, but they are valid in IL. It is preventing to refer them directly in your C# code and also it avoids the possibility of naming collisions.
First very important thing to notice is that from our lambda expression was created an internal method. There is no such concept as “lambda expression method” in IL, instead a new method with your code is created.
Second thing to notice is strange mechanism of initializing of this delegate. It is cleverness of Microsoft compiler. Because if code is ever called again, this delegate is already cached and ready to use. So if you need to cache the delegate in your application, you actually don't have to. Microsoft compiler will do job for you for free.
Expression trees
C# 3 provides built-in support for converting lambda expressions into expression trees. But before that we will need to know what expression trees actually are.
Expression trees represent code in a tree-like data structure, where each node is an expression itself. This provide an abstract way of representing some code. This can be very valuable if you want to modify or transform code before executing it. Expression trees are used for example in the dynamic language runtime (DLR) to provide interoperability between dynamic languages and the .NET Framework or execution of LINQ queries.
There are different types of expressions represent the different operations that can be performed. For example expressions for operations such as addition, comparison, negate, condition, method call, loop etc. We don't need to go through all of them; the whole list can be found on MSDN.
In the namespace System.Linq.Expressions
can be found many classes, interfaces and enumerations. The most important class is the Expression
class, which is in fact abstract class . Mostly it consists static factory methods to create instances of expressions classes.
Let’s start off by creating very simple expression tree which will tell us whether number is negative.
ParameterExpression numberParameter = Expression.Parameter(typeof(int), "n");
ConstantExpression zero = Expression.Constant(0, typeof(int));
BinaryExpression numberLessThanZero = Expression.LessThan(numberParameter, zero);
Expression<Func<int, bool>> isNegativeExpression = Expression.Lambda<Func<int, bool>>(
numberLessThanZero,
new ParameterExpression[] { numberParameter });
Func<int, bool> compiled = isNegativeExpression.Compile();
Console.WriteLine(compiled(-1)); Console.WriteLine(compiled(0)); Console.WriteLine(compiled(1));
This is very easy example. By calling overridden method ToString()
will produce human-readable output. Result is exactly what we expected.
As you see from example this is painful way to create just very simple tree expression. Luckily the compiler can help us. Answer for this trouble is lambda expression as we will see in next paragraph.
Before leaving this short description, we have to mention one very important fact - expressions are immutable. Once you create some, it’ll never change, so you can cache it and reuse for later.
Lambda expressions as expression trees
As we’ve already seen, lambda expressions can be converted to delegate instances. Second available conversion is to expression trees. Compiler is able build Expression<TDelegate>
at execution time. Let's have a look on some example
Expression<Func<int, bool>> isNegativeExpression = n => n < 0;
That's it, nothing more. Compare it with example from previous paragraph. This notation is short and readable. We can finish same example like before.
Expression<Func<int, bool>> isNegativeExpression = n => n < 0;
Func<int, bool> compiled = isNegativeExpression.Compile();
Console.WriteLine(compiled(-1)); Console.WriteLine(compiled(0)); Console.WriteLine(compiled(1));
On first line we created expression tree from lambda expression. On next line we compiled this to delegate instance. This compilation happens during the run (not compilation) time. And then on rest of lines we call this delegate to test it.
Note that not all lambda expressions can be converted to expression trees. There are couple of restrictions. But the most important is you cannot convert a lambda expression with a block of statements into an expression tree. Neither just one return statement. This restriction applies to all .NET versions.
References
History
Initial release v1.0: 24th September 2015