I will admit that I was pretty confused about Lambdas at first. There was a lot of hype about it and the syntax threw me at first. I wasn’t sure how to read (in English) what the code was trying to do. Some time ago, I decided to really bear down and figure out what this was about and I’ve decided to share what helped me so that maybe it could help someone else.
Lambdas were added with C# 3.0 (which shipped with the 3.5 Framework, which runs on the 2.0 Runtime… ah Marketing!). However, in reality, they are basically some syntactic sugar around anonymous delegates which have been around since the 2.0 Framework came out. Let’s look at this very simple code.
using System; using System.Collections.Generic; namespace Lambdas { class Program { static void Main(string[] args) { List<object> list = new List<object>() { 1, "a", 2, "b" }; List<object> justNumbers = list.FindAll(IsInt32); foreach (object i in justNumbers) { Console.WriteLine(i); } } static bool IsInt32(object input) { int i; return Int32.TryParse(input.ToString(), out i); } } }
Okay, the Find() and FindAll() methods on the List<T> take a Predicate as an argument. What a predicate is is a special kind of delegate (a way to pass around a function or method like a variable) that evaluates a specific item and determines if it is true or false for some condition. In this case, I return whether or not the object is an integer. I have named my predicate IsInt32 and I pass it by name into the FindAll method.
I could also use an anonymous delegate and just declare the bit of code inside IsInt32 inline to that FindAll method. That looks like this.
using System; using System.Collections.Generic; namespace Lambdas { class Program { static void Main(string[] args) { var list = new List<object>() { 1, "a", 2, "b" }; var justNumbers = list.FindAll( delegate (object o) {int i; return Int32.TryParse(o.ToString(), out i);}) ; foreach (object num in justNumbers) { Console.WriteLine(num); } } } }
In this case, I declare to the compiler that I am going to declare an inline method to use and that it is to be treated as a delegate (and as such can be passed around). If you compile and run both programs, you will see that they produce the same output. But none of this is a lambda. Lets take a look at the last example with lambda syntax.
using System; using System.Collections.Generic; namespace Lambdas { class Program { static void Main(string[] args) { var list = new List<object>() { 1, "a", 2, "b" }; var justNumbers = list.FindAll( (object o) => { int i; return Int32.TryParse(o.ToString(), out i); }); foreach (object num in justNumbers) { Console.WriteLine(num); } } } }
Okay, we only changed our syntax slightly. We ditched the delegate keyword and we put in this funny => symbol. What that has done is has identified an inline method a different way. In the snippet below we are defining that our method takes a parameter of type object named o and then the => means “this method then does” and then I go on to put code that would exist in that method.
(object o) => { int i; return Int32.TryParse(o.ToString(), out i); }
However, we can ignore the type definition, because the compiler can infer the type for us. So that code can then become
o => { int i; return Int32.TryParse(o.ToString(), out i); }
That doesn’t read any differently, but this syntax (the common syntax, btw) is what I’ve found confuses the most people. In fact, to prove that I’m not lying to you, I’ve built my project with that line in it (the o=>) and then looked at the code in Reflector. This is what is returned for the relevant section.
List<object> <>g__initLocal0 = new List<object>(); <>g__initLocal0.Add(1); <>g__initLocal0.Add("a"); <>g__initLocal0.Add(2); <>g__initLocal0.Add("b"); List<object> justNumbers = <>g__initLocal0.FindAll(delegate (object o) { int i; return int.TryParse(o.ToString(), out i); });
As you can see, the compiler took that syntax and merely created an anonymous delegate just like in our anonymous delegate example. You see the delegate keyword is in there, as well as the explicit “object o” declaration.
I can also take a second to “nerd out” about Reflector and point out another bit of compiler trickery. You see that I had been using the “object initializer” syntax when I declared the initial List<object> where I filled the list just by including the items after the declaration inside of the curly braces. If you see the Reflector code, the compiler still has to generate the code that does list.Add(), even if I am spared the keystrokes.
Hopefully, this has been a helpful introductory primer on Lambdas for anyone who may have been having some confusion. Another time I will take a look as to how lambda expressions can be used in LINQ.