C# – Async/await with a Func delegate

To make a Func delegate awaitable, you have to make its out parameter a Task, like this:

Func<int, Task> delayFunc = async (seconds) => 
{
	await Task.Delay(1000 * seconds);
};
Code language: C# (cs)

This Func accepts an int parameter and returns a Task. Since it returns a Task, it can be awaited.

await delayFunc(10);
Code language: C# (cs)

Notice that this isn’t returning a value. Normally you’d use an Action delegate if you didn’t want to return a value. However, you can’t make an Action delegate awaitable since it can’t return a Task. Instead, if you want an awaitable delegate, you have to use a Func with an out parameter, as shown above.

I’ll show a few more examples of using awaitable Func delegates.

Awaitable Func delegate that returns a value

To make a Func awaitable and return a value, make its out parameter a Task<T>, like this:

Func<int, int, Task<int>> delaySum = async (a, b) =>
{
	await Task.Delay(100);
	return a + b;
};
Code language: C# (cs)

This Func accepts two integer parameters and returns a Task of type int. This means when you await it, it’ll return an int value.

int sum = await delaySum(10, 10);
Code language: C# (cs)

Pass in the awaitable Func to an async method

Let’s say you have an algorithm where most of the logic is the same for all scenarios, but a small part is unique for each scenario. You can have a single method with all the common logic, and then pass in a delegate for the small part that is different. This is referred to as the strategy pattern.

If the delegate you’re passing in is an awaitable Func, then you’ll have to make the method async too.

Here’s an example. Let’s say you want to get records asynchronously from the database, serialize the records to JSON, and then save the JSON to a file. The way the records are fetched is different for each table, so you want to pass in a delegate. You want the fetching to be asynchronous, so you’ll have to use an awaitable Func delegate.

Here’s the async method that accepts the awaitable Func delegate:

private async Task SaveToFile<RecordType>(string filePath, string id, Func<string, Task<RecordType>> Get)
{
	var rowData = await Get(id);
	var json = JsonSerializer.Serialize<Type>(rowData);
	File.WriteAllText(filePath, json);
}
Code language: C# (cs)

The Func accepts a string parameter (the id of the record to fetch) and returns a Task of type RecordType (a generic type parameter). In the following example, Employee will be specified for the generic type. This means when the Func is awaited, it’ll return an Employee object.

Here’s how to call the generic async method:

await SaveToFile<Employee>(@"C:\temp\employee.json", "12345",  async (id) => await EmployeeRepo.Get(id));
Code language: C# (cs)