The New Lambda Expressions Feature in C# 3.0

C# 2.0 (released in November 2005) introduced a new feature, anonymous methods, that allows you to declare your method code inline instead of with a delegate function. Lambda expressions, a new feature in C# 3.0, have a more concise syntax to achieve the same goal. Take a closer look at anonymous methods before discussing lambda expressions.

Suppose you want to create a button that updates a ListBox on your form when you click it. In C# 1.0 and 1.1, you would do it as follows:

public MyForm()
{
   listBox   = new ListBox(...);
   textBox   = new TextBox(...);
   addButton = new Button(...);
   addButton.Click += new EventHandler(AddClick);
}

void AddClick(object sender, EventArgs e)
{
   listBox.Items.Add(textBox.Text);
}

With C# 2.0, you could have it as under:

public MyForm()
{
   listBox   = new ListBox(...);
   textBox   = new TextBox(...);
   addButton = new Button(...);
   addButton.Click += delegate
   {
      listBox.Items.Add(textBox.Text);
   };

As you can see, you don't have to explicitly declare a new method to link it with an event. You can use anonymous methods to achieve the same thing in C# 2.0. C# 3.0 introduces an even simpler syntax, lambda expressions, which you write as a parameter list followed by the "=>" token, followed by an expression or a statement block.

Parameters to Lambda Expressions

The parameters of a lambda expression can be explicitly or implicitly typed. In an explicitly typed parameter list, the type of each expression is explicitly specified. In an implicitly typed parameter list, the types are inferred from the context in which the lambda expression occurs:

(int x) => x + 1          // explicitly typed parameter

(y,z) => return y * z;    // implicitly typed parameter

Lambda Expressions Demonstration

The following code snippet from the downloadable source code uses two techniques to print out strings in a list whose length is even. The first technique, implemented in the function AnonMethod, is through anonymous methods. The second technique, implemented in the function LambdaExample, is through a lambda expression:

// Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;

namespace LambdaExample
{
   public delegate bool KeyValueFilter<K, V>(K key, V value);
   static class Program
   {

      static void Main(string[] args)
      {
         List<string> list = new List<string>();

         list.Add("AA");
         list.Add("ABC");
         list.Add("DEFG");
         list.Add("XYZ");
         Console.WriteLine("Through Anonymous method");
         AnonMethod(list);
         Console.WriteLine("Through Lambda expression");
         LambdaExample(list);

         Dictionary<string, int> varClothes= new Dictionary<string,int>();

         varClothes.Add("Jeans", 20);
         varClothes.Add("Shirts", 15);
         varClothes.Add("Pajamas", 9);
         varClothes.Add("Shoes", 9);
         var ClothesListShortage = varClothes.FilterBy((string name,
            int count) => name == "Shoes" && count < 10);
            // example of multiple parameters
         if(ClothesListShortage.Count > 0)
            Console.WriteLine("We are short of shoes");
         Console.ReadLine();
      }

      static void AnonMethod(List<string> list)
      {


         List<string> evenNumbers =
            list.FindAll(delegate(string i)
            { return (i.Length % 2) == 0; });

         foreach (string evenNumber in evenNumbers)
         {
            Console.WriteLine(evenNumber);

         }
      }

      static void LambdaExample(List<string> list)
      {
         var evenNumbers = list.FindAll(i =>
            (i.Length % 2) == 0);    // example of single parameter
         foreach(string i in evenNumbers)
         {
            Console.WriteLine(i);
         }
      }
   }

   public static class Extensions
   {
      public static Dictionary<K, V> FilterBy<K, V>
         (this Dictionary<K, V> items, KeyValueFilter<K, V> filter)
      {
         var result = new Dictionary<K, V>();

         foreach(KeyValuePair<K, V> element in items)
         {
            if (filter(element.Key, element.Value))
               result.Add(element.Key, element.Value);
         }

         return result;
      }

   }
}

The second technique is sort of interesting. It filters the list of strings based on the length of the string, but it uses a new syntax. If you have Visual Studio 2005 and LinQ Preview installed, you can use the editor to compile the application. If you do not have VS2005, you can compile the code from the command line by using the following command:

C:\Program Files\LINQ Preview\Bin\Csc.exe
/reference:"C:\Program Files\LINQ Preview\Bin\System.Data.DLinq.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Data.dll
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll
/reference:"C:\Program Files\LINQ Preview\Bin\System.Query.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll
/reference:"C:\Program Files\LINQ Preview\Bin\System.Xml.XLinq.dll"
/target:exe Program.cs

The New Lambda Expressions Feature in C# 3.0

Inside the Intermediate Language (IL)

To get under the hood of the code, fire up ILDASM and select the application. You will see something similar to the screenshot in Figure 1.

Figure 1: Sample Application in ILDASM

Double-click the AnonMethod function to see the IL generated by the C# compiler:

.method private hidebysig static void AnonMethod(class
   [mscorlib]System.Collections.Generic.List`1<string> list)
   cil managed
{
   // Code size       96 (0x60)
   .maxstack  4
   .locals init ([0] class [mscorlib]System.Collections.Generic.List
                    `1<string> evenNumbers,
                 [1] string evenNumber,
                 [2] valuetype [mscorlib]System.Collections.Generic.List
                     `1/Enumerator<string> CS$5$0000,
                 [3] bool CS$4$0001)
   IL_0000:  nop
   IL_0001:  ldarg.0
   IL_0002:  ldsfld     class [mscorlib]System.Predicate
                        `1<string> LambdaExample.Program::
                        `<>9__CachedAnonymousMethodDelegate1'
   IL_0007:  brtrue.s   IL_001c
   IL_0009:  ldnull
   IL_000a:  ldftn      bool LambdaExample.Program::
                        `<AnonMethod>b__0'(string)
   IL_0010:  newobj     instance void class [mscorlib]System.Predicate
                        `1<string>::.ctor(object, native int)
   IL_0015:  stsfld     class [mscorlib]System.Predicate`1<string>
                        LambdaExample.Program::
                        `<>9__CachedAnonymousMethodDelegate1'
   IL_001a:  br.s       IL_001c
   IL_001c:  ldsfld     class [mscorlib]System.Predicate`1<string>
                        LambdaExample.Program::'<>
                        9__CachedAnonymousMethodDelegate1'
   IL_0021:  callvirt   instance class [mscorlib]System.Collections.
                         Generic.List`1<!0> class [mscorlib]System.
                         Collections.Generic.List`1<string>::
                         FindAll(class [mscorlib]System.Predicate`1<!0>)
   IL_0026:  stloc.0
   IL_0027:  nop
   IL_0028:  ldloc.0
   IL_0029:  callvirt   instance valuetype [mscorlib]System.Collections.
                        Generic.List`1/Enumerator<!0> class
                        [mscorlib]System.Collections.Generic.List`1
                        <string>::GetEnumerator()
   IL_002e:  stloc.2
   .try
   {
      IL_002f:  br.s       IL_0042
      IL_0031:  ldloca.s   CS$5$0000
      IL_0033:  call       instance !0 valuetype [mscorlib]System.
                           Collections.Generic.List`1/Enumerator
                           <string>::get_Current()
      IL_0038:  stloc.1
      IL_0039:  nop
      IL_003a:  ldloc.1
      IL_003b:  call       void [mscorlib]System.Console::
                           WriteLine(string)
      IL_0040:  nop
      IL_0041:  nop
      IL_0042:  ldloca.s   CS$5$0000
      IL_0044:  call       instance bool valuetype [mscorlib]System.
                           Collections.Generic.List`1/Enumerator
                           <string>::MoveNext()
      IL_0049:  stloc.3
      IL_004a:  ldloc.3
      IL_004b:  brtrue.s   IL_0031
      IL_004d:  leave.s    IL_005e
   }  // end .try
   finally
   {
      IL_004f:  ldloca.s   CS$5$0000
      IL_0051:  constrained. valuetype [mscorlib]System.Collections.
                Generic.List`1/Enumerator<string>
      IL_0057:  callvirt   instance void [mscorlib]System.
                IDisposable::Dispose()
      IL_005c:  nop
      IL_005d:  endfinally
   }  // end handler
   IL_005e:  nop
   IL_005f:  ret
}    // end of method Program::AnonMethod

Now see the IL generated by the LambdaExample function. It is strikingly similar to the code generated by the LambdaExample method:

.method private hidebysig static void LambdaExample(class
   [mscorlib]System.Collections.Generic.List`1<string> list) cil managed
{
  // Code size       96 (0x60)
  .maxstack  4
  .locals init ([0] class [mscorlib]System.Collections.Generic.List
                    `1<string> evenNumbers,
                [1] string i,
                [2] valuetype [mscorlib]System.Collections.Generic.
                    List`1/Enumerator<string> CS$5$0000,
                [3] bool CS$4$0001)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldsfld     class [mscorlib]System.Predicate
                       `1<string> LambdaExample.Program::'
                       <>9__CachedAnonymousMethodDelegate3'
  IL_0007:  brtrue.s   IL_001c
  IL_0009:  ldnull
  IL_000a:  ldftn      bool LambdaExample.Program::'<LambdaExample>
                       b__2'(string)
  IL_0010:  newobj     instance void class [mscorlib]System.Predicate
                       `1<string>::.ctor(object, native int)
  IL_0015:  stsfld     class [mscorlib]System.Predicate`1<string>
                       LambdaExample.Program::'<>
                       9__CachedAnonymousMethodDelegate3'
  IL_001a:  br.s       IL_001c
  IL_001c:  ldsfld     class [mscorlib]System.Predicate`1<string>
                       LambdaExample.Program::'<>
                       9__CachedAnonymousMethodDelegate3'
  IL_0021:  callvirt   instance class [mscorlib]System.Collections.
                       Generic.List`1<!0> class [mscorlib]System.
                       Collections.Generic.List`1<string>::FindAll(class
                       [mscorlib]System.Predicate`1<!0>)
  IL_0026:  stloc.0
  IL_0027:  nop
  IL_0028:  ldloc.0
  IL_0029:  callvirt   instance valuetype [mscorlib]System.Collections.
                       Generic.List`1/Enumerator<!0>
                       class [mscorlib]System.Collections.Generic.List
                       `1<string>::GetEnumerator()
  IL_002e:  stloc.2
  .try
  {
    IL_002f:  br.s       IL_0042
    IL_0031:  ldloca.s   CS$5$0000
    IL_0033:  call       instance !0 valuetype [mscorlib]System.
                         Collections.Generic.List`1/Enumerator<string>
                         ::get_Current()
    IL_0038:  stloc.1
    IL_0039:  nop
    IL_003a:  ldloc.1
    IL_003b:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0040:  nop
    IL_0041:  nop
    IL_0042:  ldloca.s   CS$5$0000
    IL_0044:  call       instance bool valuetype [mscorlib]System.
                         Collections.Generic.List`1/Enumerator<string>
                         ::MoveNext()
    IL_0049:  stloc.3
    IL_004a:  ldloc.3
    IL_004b:  brtrue.s   IL_0031
    IL_004d:  leave.s    IL_005e
  }  // end .try
  finally
  {
    IL_004f:  ldloca.s   CS$5$0000
    IL_0051:  constrained. valuetype [mscorlib]System.Collections.
              Generic.List`1/Enumerator<string>
    IL_0057:  callvirt   instance void [mscorlib]System.IDisposable
              ::Dispose()
    IL_005c:  nop
    IL_005d:  endfinally
  }  // end handler
  IL_005e:  nop
  IL_005f:  ret
} // end of method Program::LambdaExample

From the code listing above, you can deduce that the anonymous method and the lambda expression essentially compile to the same IL internally. Hence, their executions are similar.

Lambda Expressions with Multiple Parameters

Lambda expressions can take up multiple parameters. For example, say you have a Dictionary containing the information in Table 1.

Clothing Type Count
Shirts 15
Jeans 12
Shoes 9
Pajamas 9

Table 1: Information in Dictionary

If you have an anonymous method (FilterBy) to filter the dictionary by key and value, you can call that anonymous method through lambda expressions passing multiple parameters. The accompanying source code has the implementation of FilterBy:

var ClothesListShortage = clothesList.FilterBy((string name, int count)
   => name == "Shoes" && count < 10);

Simpler Syntax

Lambda expressions provide a simple way to write inline code blocks where delegates are expected. Their behavior is strikingly similar to anonymous methods. In fact, they are syntactic sugar in terms of syntax. They can take multiple parameters, explicit or implicit.

About the Author

Vipul Patel is a Microsoft MVP (two years in a row) in Visual C# and currently works at Microsoft through Volt Information Sciences. He specializes in C# and deployment issues. You can reach him at Vipul_d_patel@hotmail.com.



About the Author

Vipul Vipul Patel

Vipul Patel is a Software Engineer currently working at Microsoft Corporation, working in the Office Communications Group and has worked in the .NET team earlier in the Base Class libraries and the Debugging and Profiling team. He can be reached at vipul_d_patel@hotmail.com

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds