devxlogo

From Delegate to Lambda

From Delegate to Lambda

elegates play a tremendously important role in developing applications for the .NET Framework, especially when using C# or Visual Basic. Events, a special application of delegates, are used all over the framework. And the application and possibilities of delegates has only grown over time. C# 2.0 introduced the concept of anonymous methods and C# 3.0 and VB 9 take anonymous methods to the next level with lambda expressions. This article reviews the evolution of delegates and examines possibilities and syntax of delegates and lambdas in .NET 3.5.

Lambda expressions are the new hot thing in .NET 3.5. Almost a core element of applying LINQ, they offer a new syntax when implementing queries. I find a lot of people at user group meetings, code camps, and conferences struggling with the syntax of lambda expressions. At the core, the problem seems to be that developers have a hard time understanding what a lambda expression is. I tell them it’s just a concise notation for a delegate. Blank faces look back. You have to really understand what a delegate is in order to understand that a lambda expression is little more than a delegate.

In this article I will look at some very basic code samples and take you from old school programming techniques to using lambda expressions in your code.

The World Without Delegates
Many programming languages don’t use delegates. Guess what: People are still able to program valuable applications using these languages. This article looks at a basic program that takes a list of numbers, uses a method to apply a filter, selects only the even numbers, and then outputs these numbers to the console. Listing 1 and Listing 2 show what such a program might look like in C# and Visual Basic respectively.

The code in the application is functionally correct; it works and life is good. You can well imagine that instead of working with a generic list of integers you could use this same style of development to filter a list of customers or a table with orders.

However, in a lot of cases you’ll find that just filtering one way is not enough. A different user might want the odd numbers or you may want to filter on customer location instead of customer name. If you proceed the way you started, you could end up having a GetOddNumbers method, a GetDivisibleByThree method, and many more custom methods. Not really efficient, right??especially because you’ll be re-implementing the same loop numerous times. It would be preferable to have just one select method with a parameter for the way you want to filter. But what should this parameter’s type be?

What Is a Delegate?
Wikipedia defines a delegate as follows:

A delegate is a form of type-safe function pointer used by the .NET Framework. Delegates specify a method to call and optionally an object to call the method on. They are used, among other things, to implement callbacks and event listeners.

I personally like my own definition (of course, I’m a little biased):

A delegate definition is a signature description for a method.

A delegate defines a “template” for the return type, parameter types, and the order of a method’s parameters.

It helps me to think of a delegate as an interface definition for methods. An interface defines a number of methods, properties, or events that need to be implemented by a class. You can use an interface definition as a type for a parameter in a method or a return type. Similarly you can use a delegate as a return type for a method or as the type for a parameter in a method.

You declare a delegate using the delegate keyword, for example:

   // In C#:   delegate void Print( string s );      ' in VB:   Delegate Sub Print(ByVal s As String)

The snippet shows the declaration of a delegate, which has a return type void and takes a string as a parameter. You use the delegate name to indicate how or what the delegate should be applied to, but it is not part of the template. The following methods match the “template” set forth by the Print delegate:

   // In C#:   // matches delegate Print   public void PrintToPaper( string text ){}      // matches delegate Print   private void PrintToConsole( string t ){}      // matches delegate Print   internal void DumpToLog( string s ){}         ' in VB:   ' matches delegate Print   Public Sub PrintToPaper(ByVal text As String)   End Sub      ' matches delegate Print   Private Sub PrintToConsole(ByVal t As String)   End Sub      ' matches delegate Print   Friend Sub DumpToLog(ByVal s As String)   End Sub

Note that all the above methods match with the delegate even though the names are not Print. They match because the return type is void and they have only one parameter of type string.

Equally the following methods are not a match with the Print delegate:

   // In C#:   // no match   public void DumpToLog( string s, int logLevel ){}   // no match   internal string FormatToUpper( string s )   {      return s.ToUpper();   }      ' in VB:   ' no match   Sub DumpToLog(ByVal s As String, _      ByVal logLevel As Integer)   End Sub      ' no match   Function FormatToUpper(ByVal s As String) _      As String      Return s.ToUpper()   End Function

The preceding functions are not a match because the DumpToLog method takes a different number of parameters and the FormatToUpper has a different return type.

Now that you know how to declare a delegate and you know when a method matches with the template defined by the delegate, how do you use the delegate? Back to the original problem: you want to filter a collection of data, in this case a collection of integers.

Editor’s Note: This article was first published in the September/October 2008 issue of CoDe Magazine, and is reprinted here by permission.

Filtering with Delegates
Your goal now is to replace the GetEvenNumbers method in Listing 1 and Listing 2 with a more generic method called Select(). You can use a delegate as a parameter type. So declare a delegate named “Filter” for this purpose:

   // In C#:   delegate bool Filter( int x );      ' in VB:   Delegate Function Filter(ByVal x As Integer) _      As Boolean

The delegate takes an integer as a parameter and returns a Boolean value. The Boolean result allows you to implement filtering methods where the “x” will be included in the result if the return value of the filtering method is true.

You can now implement the Select() method, which takes a parameter of type Filter:

   // In C#:   static List Select( List numbers,       Filter filter )   {      List result = new List();      foreach ( int i in numbers )      {         // call delegate         if ( filter( i ) == true )         {            result.Add( i );         }      }      return result;   }      ' in VB:   Function [Select](ByVal numbers As _      List(Of Integer), _      ByVal filter As Filter) _      As List(Of Integer)      Dim result As New List(Of Integer)      For Each number In numbers         ' call delegate         If filter(number) = True Then            result.Add(number)         End If      Next      Return result   End Function

The snippets show that the Select method is able to perform a select operation without knowing what the selection criteria are. You’ve succeeded in making your Select method generic and reusable in many scenarios.

It’s worth noting that working with delegates in C# 3.0 and VB9 has been simplified. The snippets show that you can use the filter variable as if it were an actual method. Previously you had to call the Invoke(?) method on the delegate, in this case filter.Invoke(i), in order to invoke the delegate.

Now that your reusable Select method is in place, you can write methods that match the delegate and use them to filter your collection of integers:

   // In C#:   static bool IsEvenNumber( int x )   {      return ( x % 2 == 0 );   }      static bool IsOddNumber( int x )   {      return ( x % 2 != 0 );   }      List even = Select( _numbers, IsEvenNumber );   List odd = Select( _numbers, IsOddNumber );   Print( even );   Print( odd );      ' in VB:   Function IsEvenNumber(ByVal x As Integer) _      As Boolean      Return (x Mod 2 = 0)   End Function      Function IsOddNumber(ByVal x As Integer) _      As Boolean      Return (x Mod 2 <> 0)   End Function      Dim even = _       [Select](_numbers, AddressOf IsEvenNumber)   Dim odd = _       [Select](_numbers, AddressOf IsOddNumber)   Print(even)   Print(odd)

Both the IsEvenNumber and IsOddNumber match the Filter delegate and you can therefore use them when calling the Select() method. The use of the delegate as a parameter type allows you to create a parameter that looks like it is a pointer or reference to a method; however, it is neither! The parameter is an object that inherits from System.Delegate. It is the C# compiler that does the magic of letting you pass in a method name as if it is a reference to the method. In C# 2.0, the syntax for calling Select() is different; you need to create an instance of the delegate object like so:

   List even = Select(       _numbers, new Filter( IsEvenNumber ) );

Also notice how VB does not perform the same magic and requires you to use the AddressOf keyword.

Stepping It Up: Anonymous Methods in C#

Author’s Note: Because VB .NET 8.0 does not support the concept of anonymous methods, this section applies only to C#.

C# 2.0 introduced the concept of anonymous methods. In the pre-C# 2.0 era, the only way to use delegates was to create a named method. In C# 2.0, Microsoft added a feature to allow inline declaration of a method using the delegate keyword and a code block. So what does this look like?

   List even =    Select( _numbers,    delegate( int x ) { return ( x % 2 == 0 ); } );

Instead of passing the name of an existing method, you can use the delegate keyword to define an inline function. The snippet shows the creation of a method that takes an integer as a parameter, has a method body, and returns a Boolean value; in other words, the anonymous method matches with the Filter-delegate.

As you will probably agree, this does nothing for readability-in the C# 2.0 era, the use of this type of inline delegate creation was largely reserved to generated code. However, out of the darkness came light and now lambda expressions are here to provide a readable syntax for this kind of inline delegate creation.

Lambda Expressions: Readable Anonymous Methods
Lambda expressions allow you to create delegates in a very concise syntax. And good news for the VB developers: VB 9.0 also supports lambda expressions!

Lambda Expressions in C#
Here’s the definition of lambda expressions from the C# Programming Guide on MSDN):

A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types.

All lambda expressions use the lambda operator =>, which is read as “goes to”. The left side of the lambda operator specifies the input parameters (if any) and the right side holds the expression or statement block. The lambda expression x => x * x is read “x goes to x times x.”

So a lambda expression is an anonymous method. In the previous section, you saw that anonymous methods are delegates; therefore lambda expressions are nothing more than delegates.

Author’s Note: In Visual Basic, the RemoveHandler statement is an exception to the lambda = delegate rule. You cannot pass in a lambda expression for the delegate parameter of RemoveHandler.

You can substitute a named method for a lambda expression when you call the Select method:

   Filter f = x => x % 2 == 0;   List even = Select( _numbers, f );

The snippet shows that you’re defining a variable f of type Filter and assigning a lambda expression to it. You then use the delegate to call the Select method.

I want to zoom in on the following lambda expression:

   x => x % 2 == 0

This lambda expression contains an input variable x that goes to x modulo 2 and compares the result to 0, returning a true/false value. Notice how there are no brackets {} and there is no return keyword. If a lambda expression consists of just one line, the result of that one line is automatically assumed to be the return value and brackets are not needed:

   x => { x++; return x % 2 == 0; }

The above snippet increments the value of x by 1 before performing the modulo 2 division. Because the code block consists of more than one statement, you now need the brackets and you also need to include the return keyword.

Another significant feature is the ability of the compiler to infer the types involved in the lambda expression. Because a lambda expression is always assigned to a delegate, the compiler will look at the types expected by the delegate and use those when validating your code. For instance, changing the lambda expression as shown below would change the return type of the expression from a Boolean to an integer:

   x => x % 2

The Filter delegate expects a Boolean return type, hence your code will not compile.

Lambda Expressions in Visual Basic
From the VB Programming Guide on MSDN:

A lambda expression is a function without a name that calculates and returns a single value. Lambda expressions can be used wherever a delegate type is valid.

Because VB never supported anonymous methods, there is no reference to them. Instead it is instantly made clear that lambda expressions are delegates. The syntax for lambda expressions in VB adds the Function keyword, going back to the Select method. Calling the Select method using a lambda expression would look like this:

   Dim f As Filter = Function(x) x Mod 2 = 0   Dim even = [Select](_numbers, f)

Again, there is no Return statement. In VB a lambda expression can consist of only a single line, because there’s no End Function statement. Just like C#, the framework infers the type of x from the delegate to which the lambda expression is assigned.

Note of caution: If you use VB with the Option Strict Off setting, then side effects may occur. The following code performs an implicit cast from Integer to Boolean, which is probably not the desired effect. It is better to use the Option Strict On setting:

   Dim f As Filter = Function(x) x Mod 2   Dim even = [Select](_numbers, f)

The new syntax offered by lambda expressions can be fairly confusing, that is, until you realize what they are. The concise notation allows for very readable and powerful programming.

Filtering and simple operations is where lambda expressions excel and lambda expressions are one of the cornerstones of the LINQ extensions methods.

LINQ and Lambdas
Lambda expressions are a short, concise way of defining delegates. You use lambda expressions a lot in LINQ. The vast majority of LINQ methods use delegates to allow you to sort, filter, project, and take action on collections of objects. LINQ adds all these functions to existing collections by implementing them as extension methods to IEnumerable (in C#), or IEnumerable(Of T) (in VB). With the code that you’ve written so far, you’re only two steps away from creating your own mini-LINQ. If you move the Select method into a public static class and make it public then you can turn it into an extension method:

   // In C#:   public static List Select(       this List numbers, Filter filter )   {      List result = new List();      foreach ( int i in numbers )      {         if ( filter( i ) == true )         {            // call delegate            result.Add( i );         }      }   return result;   }   // call extension method   List even =        _numbers.Select( x => x % 2 == 0 );      ' in VB:   Imports System.Runtime.CompilerServices   Module MiniLINQ        _   Public Function [Select]( _   ByVal numbers As List(Of Integer), _   ByVal filter As Filter) _   As List(Of Integer)   Dim result As New List(Of Integer)   For Each number In numbers   ' call delegate   If filter(number) = True Then      result.Add(number)   End If   Next   Return result   End Function   End Module   ' call extension method   Dim even = _       _numbers.Select(Function(x) x Mod 2 = 0)

The code above looks extremely similar to what it will look like when you use the LINQ extensions. Of course, the LINQ extensions are much more powerful since they are all made to be generic-your extension can deal only with lists of integers, but the concept is exactly the same.

In this article, you’ve seen how delegates can help implement generic functionality. The use of delegates has grown as the .NET Framework and .NET Runtime has grown; from a first implementation to anonymous methods, we have now arrived at lambda expressions: a short, concise way of defining delegates.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist