Lambdas provide shortcuts for sorting, filtering, finding and working with information in lists, making your code easier to read and write. Most .NET tools and techniques have names that describe their purpose. Take Language INtegrated Query (LINQ) for example. The name implies that you can write queries directly in your code. And Windows Communication

Framework (WCF) clearly indicates that its purpose is for communication between applications. But lambdas? Depending on your college path it may conjure up visions of frat parties or nightmares of lambda calculus, neither of which have much to do with improving your development productivity.

Lambdas are basically unnamed, inline functions.

Lambdas are basically unnamed, inline functions. They were new in Visual Basic 9 and C# 3.0 (.NET 3.5). Combine lambda expressions with the large set of extension methods for working with lists (also added in .NET 3.5) and you have a feature that is very powerful.

Combine lambda expressions with the large set of extension methods for working with lists (also added in .NET 3.5) and you have a feature that is very powerful!

Lambdas are much easier to understand by way of examples. Though there are many ways you can use lambdas in your application, from creating reusable inline functions to defining event handlers to building callbacks, this article focuses on the features you may use the most: sorting, filtering, finding, and working with information in a list. (For the full source code demonstrating lambdas in both Visual Basic.NET and C#, download the code examples for this article.)

Finding an Item in a List

Most applications retain lists of things. Your code may retrieve a set of order types from a database and store them in an array or a generic list so they can be displayed in a combo box. Or you may retain the list of all current customers for display and maintenance in a grid.

Let’s use the customer example. Here is a simple start for a Customer class:

    public class Customer
    {
        public int CustomerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmailAddress { get; set; }
        public bool IsDirty { get; set; }
    }

Normally, you would get your customer data from a table or other data source, but for the purpose of this example, this code builds the list:

List<Customer> custList = new List<Customer>
    {new Customer()
          { CustomerId = 1,
            FirstName="Bilbo",
            LastName = "Baggins",
            EmailAddress = "bb@hob.me"},
    new Customer()
          { CustomerId = 2,
            FirstName="Frodo",
            LastName = "Baggins",
            EmailAddress = "fb@hob.me"},
    new Customer()
          { CustomerId = 3,
            FirstName="Samwise",
            LastName = "Gamgee",
            EmailAddress = "sg@hob.me"},
    new Customer()
          { CustomerId = 4,
            FirstName="Rosie",
            LastName = "Cotton",
            EmailAddress = "rc@hob.me"}};

Now that you have a list, say you want to find the customer that has an ID of 4. You could accomplish this goal using a for/each loop:

Customer foundCustomer = null;
    
foreach (var c in custList)
{
   if (c.CustomerId == 4)
   {
      foundCustomer = c;
      break;
   }
}

Or with a LINQ statement:

Customer foundCustomer = null;
    
var query = from c in custList
            where c.CustomerId == 4
            select c;
if (query.Count() > 0)
    foundCustomer = query.ToList()[0];

In both cases, the syntax requires quite a few lines of code. You can accomplish the same task with a single line of code using lambda expressions.

In C#:

var foundCustomer = custList.FirstOrDefault(c =>
                        c.CustomerId == 4);

The => is called the lambda or “goes to” operator. It separates the lambda expression parameter(s) from the expression itself.

As you would expect, Visual Basic (VB) uses a word to define the inline function instead of a symbol. The lambda expression parameters are then expressed as normal function parameters.

In VB:

Dim foundCustomer = _
        custList.FirstOrDefault(Function(c) _
                          c.CustomerId = 4)

The FirstOrDefault method used in the above examples finds the first entry in the list that matches the predicate defined in the lambda expression. If no entry is found, it returns the default value of the list item (Customer in this case), which by default is null/nothing for reference types.

In these examples, the lambda parameter, c, represents each item in the list, in this case an object of type Customer. Notice that the type of the parameter is not defined in the VB or C# code. Lambda expressions leverage implicit typing. Without implicit typing, the C# code example looks like this:

Customer foundCustomer =
      custList.FirstOrDefault((Customer c) =>
                        c.CustomerId == 4);

The lambda parameters must be in parenthesis unless there is only one parameter and it uses implicit typing. If there are no parameters, empty parentheses are required: () => expression.

Here is the VB code example without implicit typing:

Dim foundCustomer As Customer = _
 custList.FirstOrDefault(Function(c As Customer) _
                          c.CustomerId = 4)

As these examples show, you can save time in reading and writing your code using lambda expressions to find items in a list. You can save even more time using the implicit typing feature with your lambda expressions.

Working with Lists

Along with lambda expressions, .NET 3.5 introduced many new extension methods on the .NET Framework IEnumerable interface. IEnumerable is implemented by most lists in .NET including arrays and generic lists. Any list that allows you to use a for/each loop implements IEnumerable. The FirstOrDefault method in the earlier examples is an extension method on IEnumerable.

To view the available methods of a list, use IntelliSense. Declare an array or generic list variable as shown below. Then use IntelliSense to get the list of available methods as depicted in Figure 1.

Figure 1: Use IntelliSense to view the list of available extension methods.

In C#:

List<Customer> custList = new List<Customer>

In VB:

Dim custList As New List(Of Customer)

Many of the list methods take a delegate as a parameter. A delegate is basically an object-oriented function pointer with a specific set of parameters and an optional return value. You can pass the name of a function as the method parameter as long as the function matches the signature defined by the delegate.

In C#:

var foundCustomer =
    custList.FirstOrDefault(new Func<Customer,
                   bool>FindCustomer);

This code passes in a named function that operates on a Customer object and returns a bool value for each item in the list. Looking at the IntelliSense for the FirstOrDefault function, you can see that this matches the expected delegate signature.

In VB:

Dim foundCustomer = _
   custList.FirstOrDefault(AddressOf FindCustomer)

The AddressOf operator in VB takes care of defining the appropriate function signature, so you don’t have to declare it as in the C# example.

You can use a lambda expression anywhere a delegate is required and keep your code encapsulated.

But using a named method requires that you then write the defined function, in this case FindCustomer. This technique sprinkles helper methods throughout your code, making it less maintainable. Instead, you can use a lambda expression anywhere a delegate is required and keep your code encapsulated.

Most list method parameters require one of three different types of delegates:

  • Predicate
  • Action
  • Func

Each of these delegate types are detailed below followed by examples, since examples are the easiest way to see how each of these work.

A predicate is basically a function that returns a Boolean value (true or false). A predicate delegate defines a method that takes one parameter and evaluates to true or false. The FindAll method of an array or list is an example of a method that uses a predicate delegate as a parameter (Figure 2). You can pass in a lambda expression that takes one parameter and evaluates to True or False.

Figure 2: IntelliSense shows that the FindAll method requires a predicate delegate as a parameter. You can define that predicate using a lambda expression.

The FirstOrDefault method described earlier is a great way to find a single item in a list, but FindAll lets you find a set of items, basically filtering the list to match a specific condition. Using the customer example, a search feature allows filtering the set of customers to those with a last name that begins with a user-entered value.

In C#:

var filteredList = custList.FindAll(c =>
           c.LastName.StartsWith("Ba"));

In VB:

Dim filteredList = custList.FindAll(Function(c) _
           c.LastName.StartsWith("Ba"))

The lambda expression parameter, c, again represents each item in the list. For each item, the predicate expression is evaluated to true or false. A customer’s last name either does or does not start with “Ba”. If it is does, FindAll includes the item in the filtered list; otherwise not.

An action delegate defines a method that takes a set of parameters and has no return value. Its purpose is to perform an action. The ForEach method of a generic list is an example of a method that uses an action delegate as a parameter. You can pass in a lambda expression that matches the defined action delegate.

Now suppose you want to perform some action on all of the items in the list. Maybe something simple like displaying them in the Debug window to confirm that the filtering returns the correct results.

In C#:

filteredList.ForEach(c =>
           Debug.WriteLine(c.LastName));

Normally, you would need at least three lines of code to display each customer name in the Debug window using a For/Each statement. Using the ForEach method with a lambda expression does the same thing in one line.

In VB:

filteredList.ForEach(Sub(c) _
          Debug.WriteLine(c.LastName)) 'Error!

To define a function without a return value in VB, you need to use the Sub keyword instead of Function. However, this is not available in .NET 3.5. It will be available in .NET 4.0. So you cannot currently use lambda expressions as action delegates in VB.

You cannot currently use lambda expressions as action delegates in VB.

A func delegate defines a method that optionally takes a set of parameters and returns a value. Its purpose is to perform an operation. The Any method of a generic list is an example of a method that uses a func delegate as a parameter. You can pass in a lambda expression that matches the defined func delegate.

Another common operation on a list is to determine if any of the items in the list match specific criteria. For example, an isDirty property of the Customer class defines whether the customer data has been changed by the user. You then want to display a message upon exit if the user has unsaved changes; basically you want to see if any of the customers have the isDirty flag set. The Any extension method makes this easy.

In C#:

bool hasChanges = custList.Any(c =>
                               c.IsDirty);

In VB:

Dim hasChanges = custList. Any(Function(c) _
                               c.IsDirty)

You can also check that all items meet specific criteria using the All extension method. For example, an IsValid property of the Customer class defines whether the customer data is valid. You then want to display a message unless all of the customer data is valid. The All extension method is similar to the Any extension method above.

Sorting and Aggregating

What if you want to do something more complex, such as filtering and then sorting? You can chain methods together to perform multiple operations.

You can chain methods together to perform multiple operations.

For example, you want to find the list of customers with last names starting with “Ba” and sort the results by first name.

In C#:

var filteredList = custList.Where(c =>
            c.LastName.StartsWith("Ba"))
            .OrderBy(c => c.FirstName);

In VB:

Dim filteredList = custList.Where(Function(c) _
           c.LastName.StartsWith("Ba")) _
           .OrderBy(Function(c) c.FirstName)

This code uses the Where extension method to filter the list to those whose last name starts with a specific set of characters and then sorts by the first name.

Say you need to aggregate one value from each object into a single string. For example, you want to send an e-mail to a set of customers. This requires a string with the e-mail addresses separated by a semicolon (;).

In C#:

string email = custList.Select(c =>
           c.EmailAddress)
      .Aggregate((items, item) =>
           items + "; " + item);

In VB:

Dim email As String = _
    custList.Select(Function(c) c. EmailAddress) _
    .Aggregate(Function(items, item) _
           items & "; " & item)

The Select method selects the EmailAddress for each customer. The Aggregate method builds a list of the items based on the lambda expression. The result:

"bb@hob.me; fb@hob.me; sg@hob.me; rc@hob.me"

Notice that this did not require any extra code to ensure there is no extra semi-colon at the beginning or end of the list, which often happens when using a loop to concatenate text.

Note: Be careful when using the Aggregate method on strings because it is very inefficient on large numbers of strings. Consider using the String Join method instead.

All of the examples so far have shown single-line expressions. In C#, you can also have multi-line lambda expressions. (VB 10 will support multi-line lambdas.) This can be helpful for debugging such as this example:

bool hasChanges = custList.Any(c =>
    {
        if (c.IsDirty)
        {
            Debug.WriteLine(c.CustomerId);
            return true;
        }
        else
            return false;
    });

Notice the return statements. In a single-line lambda, the return is implied. With multi-line lambdas, you need to add the return statements as appropriate.

Conclusion

This article covered many of the different ways you can use lambda expressions to work with your lists. But many more methods are available that support lambda expressions. Use IntelliSense to review the list of methods and give them a try.

It takes a little time to get used to the syntax, but once you start using lambdas you will find many practical uses for them in your everyday coding.