Delegate types and lambda expressions have been a part of the .NET tool belt for a while now, so they show up everywhere. If you've ever used StructureMap, AutoMapper, ASP.NET MVC and the like, then you've surely run into them. Despite this, delegates and lambdas tend to confuse developers when first encountered. Even when you get comfortable with the syntax as it's used in your favorite library, you might not really be familiar with what they are or how they allow for such seemingly magical statements.
Let's pretend that delegate types and lambdas don't exist yet, and then reinvent them in terms of other .NET concepts that are more widely understood.
Suppose we have been given an IEnumerable<Customer> and we want to filter that down to the list of those customers who live in Texas. Without delegates, we could write the obvious loop like so:
Yeesh. I have written foreach-if-add loops a thousand times. Whenever we find ourselves writing virtually the same thing again and again, we should take a step back and think, "Is there a deeper concept here I could pull out into a library?" We have some subtle code duplication whenever we write foreach-if-add loops.
To remove duplication, we first identify what parts vary from one instance of the pattern to the next. For foreach-if-add loops, the things that vary are the type of the items being filtered, and the if-statement's condition expression. To bottle up the foreach-if-add loop, we need a new method that takes in the condition as an argument. Without delegates, we could deal with this using an interface:
Ok, so we've pulled out the foreach-if-add loop into a reusable
Where extension method. Still, though, that's a lot of boilerplate code: every time we want to call
Where with a new filter, we have to bottle it up in a whole new class.
Let's start removing boilerplate code until we're left with delegates! With this example, we can imagine a whole family of interfaces that have one thing in common: interfaces with a single method named
RunMe, whose implementing classes contain only that method. If we knew this family of interfaces only have one method, and its name was always
RunMe, then we could avoid even writing its name:
condition.RunMe(item) could be shortened to simply
condition(item). Since the relevant part of a single
IFilterCondition implementation is the body of the now-nameless method, we should be able to simply provide the code for that method inline:
We can imagine that this use of the
delegate keyword is just shorthand for an instance of a class implementing our single-method interface.
Since most of these small unnamed classes exist just to evaluate and return a single expression, we might as well get rid of the
return keyword and braces. Since we already know what the item type T is, we might as well leave out the type declaration of the
customer parameter, allowing the compiler to fill in the blank. We'll need something between the argument list and the return expression so the compiler knows which is which, so we introduce the => operator. This makes the
delegate keyword redundant, leaving us with some valid (and clean!) C#:
There's a lot more to delegates and lamda expressions, but in most cases I find it easiest to reason about them as shorthand for the interfaces and classes we would have otherwise used.