Share via


C# 3.0 Lambda Expressions and Expression Trees

From the first days of C# 1.0 we could do stuff like this

 public static int Add(int x, int y)
{
   return x + y;
}

and also C# gives you the ability to write the function signature in the form of delegate, that allows function pointers so you can do something like this

 delegate int MathOperation(int x, int y);


public static int ApplyFunction(int x, int y, MathOperation operation)
{
   return operation(x, y);
}

public static int Add(int x, int y)
{
   return x + y;
}

public static void Main()
{
   int result = ApplyOperation(1, 3, new MathOperation(Add));
   Console.WriteLine(result);
}

and so this gives the ability to apply any other function you want as long as you provide a function that has the same signature...

Then in C# 2.0 we were introduced to Anonymous methods, which gives you a faster way of writing functions in case that these functions are going to be used only one time
like this

 delegate int MathOperation(int x, int y);

public static void Main()
{
   MathOperation op = new MathOperation(delegate(int x, int y)
                        {
                            return x + y;
                        });
   Console.WriteLine(op(10, 5));
}

So, this syntax is very much faster in writing delegate functions that will be used only one time.
and also, this syntax allows the new function to access the local variables in the outer scope of it, which means in the last method if the Main has a variable called myVar, the new delegate function op can access this myVar without passing it to the function.

Now after we see the case of C# 1.0, 1.1 and 2.0. Let's see what C# 3.0 says about this..
C# 3.0 introduces Lambda Expressions and Expression Trees,
lamdba expressions gives a very fast way to define delegates like in dynamic languages (e.g. Python)
so as an example .
 

 delegate int MathOperation(int x, int y);

public static void Main()
{
   MathOperation op = (i, j) => i + j;
   Console.WriteLine(op(1,2));

}

so it is really easy to define functions using lamdba expressions, first put all the function parameters comma separated then => then the expression that evaluates the result.

Also because C# gives you a predefined delegate called Func that can be used for most cases of function delegates because it is a generic delegate in this form

 delegate T Func<A, T>(A param)
or
delegate T Func<A0, A1, T>(A0 param0, A1 param1)
or
delegate T Func<A0, A1, A2, T>(A0 param0, A1 param1, A2 param2)
or
delegate T Func<A0, A1, A2, A3, T>(A0 param0, A1 param1, A2 param2, A3 param3)

So you can define any lambda expression without writing the delegate just using this one like this, lambda expression that checks for even number

 Func<int, bool> isEven = i => (i&1) == 0

so from the Func definition in this case, it takes an integer parameter and returns bool result

Also C# introduces Expression trees, that you can build a new expression and compile it and use it in runtime
so we can rewrite the Add function using the expression trees and it will look like this

 ParameterExpression p0 = Expression.Parameter(typeof(int), "x");
ParameterExpression p1 = Expression.Parameter(typeof(int), "y");

Expression<Func<int, int, int>> addExpr= Expression.Lambda<Func<int, int, int>>(
                       Expression.Add(p0, p1), new ParameterExpression[]{p0, p1});
Console.WriteLine(addExpr.Compile()(1,2));

If you used Lisp before you'll be familiar with this code, because you first put the operation then put the parameters so a simple addition will looke like this
+ x y
and that what you get if you printed out the addExpr.Body
Add(x, y)
If you noticed the expression tree we built for simple addition, you will find it relatively long, but the good news that the compiler can do this for me, all what I need to do is assigning the Lambda expression to the Expression<> object like this

 Expression<Func<int, int, int>> lambdaAdd = (i, j) => i + j;

and this will give the same result as writing the long epxression by hand.
All these stuff are the backbone of LINQ project and the great enhancements, like using sql-like syntax on collections like this

 var list = new List<int>();
for(var i =0; i<100; i++)
   list.Add(i);
var q = list.Select(i=>i).Where(i=> i>10 && i<20);
foreach(var i in q)
   Console.WriteLine(i);

in this code we can get the numbers between 10 & 20 in the list that contains numbers from 0 ~ 99, and in this code we are using the Lambda expressions in the Sequence extension methods.
also the same query

 var q = list.Select(i=>i).Where(i=> i>10 && i<20);

can be replaced with

 var q = from i in list where i > 10 & i <20 select i;

and this how LINQ was built in the first place, it is all matter of expressions

kick it on DotNetKicks.com