In the last post we explored what a lambda is, and what that weird x=>x
syntax is all about.
Now we know what it is, you might be wondering where can you use them and how might you write your own code so that a lambda can be used.
The answer is delegates!
Delegates act as a kind of placeholder for a method.
Let's take an example.
static void SaveToDatabase(Person person)
{
try
{
// save here
}
catch (Exception)
{
Console.WriteLine("could not save");
}
}
This method theoretically saves a person object. If something goes wrong, we catch the exception and log an error message to the console.
Plug 'n' Play behaviour
Now imagine we're going to call this SaveToDatabase
method from various places and want to be able to change how we log this message on a case by case basis.
For example, instead of writing to console, sometimes we might choose to send an email instead.
One way to tackle this would be to make it possible for SaveToDatabase
to take in a method which does the actual error logging. Then we can choose whether to pass in a method which writes to the console or a method which sends an email.
static void SaveToDatabase(Person person, Action onError)
{
try
{
// save here
}
catch (Exception)
{
onError();
}
}
SaveToDatabase
now indicates that it expects to receive a method (onError
).
The Action
type here defines our onError
argument as a delegate which takes zero arguments and doesn't return anything.
At this point SaveToDatabase
doesn't know anything about what onError
does, it merely knows its shape (a method with no arguments which returns void) and can "invoke" it whenever it likes, using this syntax...
onError();
This frees us up to pass in different methods when we call SaveToDatabase
.
Like so...
static void Main(string[] args)
{
var person = new Person { Name = "bob" };
SaveToDatabase(person, WriteErrorToConsole);
}
private static void WriteErrorToConsole()
{
Console.WriteLine("Could not save");
}
Here we've created a method which takes no arguments and returns void (fulfilling the requirements for onError
) and passed a reference to it along to SaveToDatabase
.
Note we're not invoking WriteErrorToConsole
in the Main
method.
At no point do we do this...
WriteErrorToConsole()
Because that would immediately invoke the method and write a message to the console.
What we want is to pass that method along to SaveToDatabase
which will invoke the method at a time of its own choosing.
Remember in the last post we discovered we can take a method like WriteErrorToConsole
and "inline" it using lambdas?
We can do something very similar here...
static void Main(string[] args)
{
var person = new Person { Name = "bob" };
SaveToDatabase(person, ()=> Console.WriteLine("Could not save"));
}
We can't use x=>x
because onError
doesn't expect any arguments. Instead, we use empty parenthesis which serve the same purpose, but for Actions which take zero arguments.
I want to say something else
What if we don't want to write "Could not save" to the console, but provide a more specific message (which varies depending on the actual error)?
For this we need to make our onError
action accept some kind of error message...
catch (Exception ex)
{
onError(ex.Message);
}
Whichever method gets passed in (the one which sends an email, or the one which writes to console) could make use of this message string and do with it as it pleases.
To make this work we need to tweak our declaration of the onError
Action slightly.
static void SaveToDatabase(Person person, Action<string> onError)
{
try
{
// save here
}
catch (Exception ex)
{
onError(ex.Message);
}
}
Now we're using Action<string>
which means we're expecting a method which takes a single string argument and returns void.
Our Main
method can take that argument and use it when writing to the console.
static void Main(string[] args)
{
var person = new Person { Name = "bob" };
SaveToDatabase(person,
message => Console.WriteLine($"Could not save, error: {message}"));
}
Delegates delegates everywhere
As you've seen, delegates are pretty powerful and can be a great way to use the same function to do slightly different things depending on what delegate you pass in.
Here we've stuck to Action
delegates but you can also use something called a Func
.
More on that next time.
Top comments (1)
Nice post Jon. Having used them quite extensively on one project a good few years ago, I'd almost forgot they existed. Thanks for the reminder!