WTF is an Action Delegate?

February 7, 2019 · 4 minute read · Tags: csharp

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.

Join the Practical ASP.NET Newsletter

Ship better Blazor apps, faster. One practical tip every Tuesday.

I respect your email privacy. Unsubscribe with one click.

    Next up

    WTF Is a Lambda?
    When you first see a Lambda you might be left scratching your head, wondering what on earth that funky syntax is all about?!