Skip to main content

C# - Delegates and lambdas 101 [Beginner]

 

If you search for definitions of the word "delegate", you might find phrases such as “transferring power to someone” or “assign a task to a person”. Those phrases describe very well what delegates in .NET are about. In short they provide an ability to pass a reference to a method to some code that can then invoke that method.

The purpose of this article is to demystify delegates and help you gain some understanding about how different ways of using delegates relate to each other.

Let’s start with a simple sample of delegate usage:
class Program
{
    static void Main(string[] args)
    {
        PerformMathOperation(Add);
        PerformMathOperation(Subtract);
    }

    public static void PerformMathOperation(Func<int, int, int> func)
    {
        int result = func(5, 4);
        Console.WriteLine(result);
    }

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

    public static int Subtract(int x, int y)
    {
        return x - y;
    }
}
 
The most interesting method in this code sample is PerformMathOperation. It takes one parameter of the type Func<int, int, int>. This is a generic delegate that takes two int values as parameters, and that returns an int value. This means that this delegate can represent any method fulfilling that signature. Note how the name of the method is completely irrelevant from the delegate perspective. All that matters is the signature, and the signature is the combination of the type of the return value, the number of parameters and their types.

In the code sample above, I have left out the creation of the actual delegate object; this is taken care of automatically by the C# compiler. The statement PerformMathOperation(Add) is identical to PerformMathOperation(new Func<int, int, int>(Add)), only shorter and – in my opinion – easier to read.

In the main method, I call PerformMathOperation twice. The first time I pass a delegate referencing the Add method, and the second time I pass a delegate referencing the Subtract method. Inside PerformMathOperation the code will invoke whatever method the delegate is referencing, collect the result and print it to the console. The output will look like this:
9
1
 
We can clearly see how the program has first called Add, and then Subtract. This is in a nutshell what delegates are all about; instead of passing a value you can pass a method that will carry out some action when invoked and optionally return a value.

enter image description here 

In the color coded code above, I have inserted some highlights to clarify some elements of the code. Orange is where delegates are created, getting a reference to a method. Blue is where the delegate is invoked (effectively executing the method it is referencing). Purple is the actual method body of the methods being called through delegates, and yellow are the parameters for those methods. 

As you can see, a method is not executed when becoming referenced by a delegate, but when the delegate is invoked. This is important to keep in mind: delegates are just an object carrying a reference to a method that may be invoked later. As such they can also be stored in variables for later use:
Func<int, int, int> dlgt = Add;

// ... and then later on:
int result = dlgt(5, 4); // result will be 9
 
As I mentioned, the delegates can be created by writing the full form (new Func ...) or a shorter form, leaving some of the work to the compiler. This has no effect of any kind on the resulting code. The same goes for invoking the delegate. In the previous code sample, the delegate variable is used as if it was a method like any other method. Again, there is some compiler magic behind the curtains. In fact, the following three code blocks will result in the exact same code coming out from the compiler:
// first
Func<int, int, int> dlgt = Add;
int result = dlgt(5, 4);

// second
Func<int, int, int> dlgt = new Func<int, int, int>(Add);
int result = dlgt(5, 4);

// third
Func<int, int, int> dlgt = new Func<int, int, int>(Add);
int result = dlgt.Invoke(5, 4);
Which one of these you choose to use is entirely a matter of style.

 

Lambdas an anonymous methods


One of the most common places where you get in touch with delegates are when using LINQ. The following code sample illustrates a common use-case:
static void Main(string[] args)
{
    IEnumerable<string> inputData = new[]
    {
        "Jimi Hendrix",
        "Janis Joplin",
        "Curt Cobain"
    };

    IEnumerable<string> result = inputData.Where(s => s.Contains("x"));
    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}
 
I have a sequence of strings, called inputData and want to filter out all strings containing the letter "x", so I use the LINQ extension Where to do this:
inputData.Where(s => s.Contains("x"));
 
Let's look at a color-coded version of that statement to compare it to the delegate usage samples above: 
enter image description here

Again, orange is where the delegate is created, purple is the method body of the code being executed and yellow are the parameters. In contrast to the previous samples, we use an anonymous method, so the delegate creation and method declaration happens in the same place, but the elements are still the same.

Somewhat simplified we can say that whatever appears on the left-hand side of => is equivalent to the parameters of a method declaration (whatever appears within the paranthesis), while whatever appears on the right-hand side of => is the method body.

Also, just as in previous examples the C# compiler takes care of some of the "heavy lifting", figuring some things out for so I don't have to write them. I'll get back to that later.

If you check how the Where method is declared, it looks like this:
public static IEnumerable<tsource> Where<tsource>(
    this IEnumerable<tsource> source,
    Func<tsource , bool> predicate
)
 
As you can see, this is an extension method for IEnumerable<T> (that is revealed by the fact that the method is static, and that the first parameter is declared with the this keyword), so it can be called for any IEnumerable<T>, such as our IEnumerable<string> inputData.

The second parameter is a Func<tsource, bool>. This is a delegate type, that refers a method taking one parameter of type tsource, and that returns a bool.

Like with "invisible" delegate declaration above, the C# compiler kindly helps out here as well. Looking at the declaration of the Where extension method, you can see that it has a generic type argument: Where<tsource>. The type argument is inferred by the compiler, so you typically do not need to type it into your code.

The statement inputData.Where(s => s.Contains("x")); is identical to inputData.Where<string>(s => s.Contains("x")); but since inputData is a sequence of strings, the compiler can figure out what type argument to use.

In our example (which is the typical way of writing a simple lambda expression when using LINQ), we have left it up to the compiler to fill in quite a few blanks. If we were to provide the full code, and without the lambda operator, it would look like this instead:
inputData.Where(new Func<string, bool>(delegate(string s) { return s.Contains("x"); }));

While this looks drastically different, there are elements that we can recognize: the parameter s in the method declaration, the s.Contains("x") in the method body. In this code sample, they play exactly the same role as they do when constructing the code using the lambda operator.

Let's transform this somewhat chatty code back to the very short lamdba expression, step by step. First let's use the lambda operator to remove the explicit delegate declaration:
inputData.Where((string s) => { return s.Contains("x"); }));
 
Now it becomes evident that what we do is to essentially remove all code that is about declaring the delegate, but we keep the parameter declaration and the method body, separated by the lambda operator.

Next, we can let the compiler infer the type of the input parameter:
inputData.Where((s) => { return s.Contains("x"); });
 
Since we operate on a list of strings, the input to the method must obviously be of the type string. Next, since the method body consists of only one line, we can remove the curly braces.

Also, when destilling the method body in the lambda expression down to a one-liner, we can (and even must) remove the return keyword:
inputData.Where((s) => s.Contains("x"));
 
Finally, since the number of parameters to the method is one, we can remove the paranthesis around it:
inputData.Where(s => s.Contains("x"));
 
All these code snippets result in the same code.

As you can see, there are a few different ways you can write code that involves declaring and using delegates, but they all end up rendering almost the exact same kind of code in your application, and there is no magic going on, just a helpful compiler making some things easier.

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...