Assume we have the following type, the Spy.

public sealed class Spy
{
  public string FirstName { get; }
  public string Surname { get; }
  public Spy(string firstName, string surname)
  {
    FirstName = firstName;
    Surname = surname;
  }
}

You can see here that it has a constructor that sets the FirstName and Surname.

Suppose we want to improve our Spy and play a theme song during the construction of the object.

We first introduce the method:

// Play theme song here
public async Task PlaySong()
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    Console.WriteLine("Playing that song!");
}

Then we call it from the constructor.

public Spy(string firstName, string surname)
{
    FirstName = firstName;
    Surname = surname;
    await PlaySong();
}

This code will fail to compile with the following errors:

  AsyncConstructors failed with 1 error(s) (0.1s)
    /Users/rad/Projects/blog/BlogCode/2025-05-29 - Async Construction/Spy.cs(12,13): error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.

Build failed with 1 error(s) in 0.5s

You might try to make the constructor async but that is impossible - the constuctor must return the object, and currently you cannot change the return type of the constructor.

There is, however, an elegant solution to this problem.

You can make a factory method that will be responsible for calling the constructor and running any async methods we may have.

public sealed class Spy
{
    public string FirstName { get; }
    public string Surname { get; }

    private Spy(string firstName, string surname)
    {
        FirstName = firstName;
        Surname = surname;
    }

    // Play theme song here
    public async Task PlaySong()
    {
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine("Playing that song!");
    }

    /// <summary>
    /// Factory method to return a spy
    /// </summary>
    /// <param name="firstName"></param>
    /// <param name="surname"></param>
    /// <returns></returns>
    public static async Task<Spy> CreateSpy(string firstName, string surname)
    {
        // Call private constructor
        var spy = new Spy(firstName, surname);
        // Call async method
        await spy.PlaySong();
        return spy;
    }
}

A couple of things to note:

  1. The factory method is static and async
  2. The constructor is private - and this cannot be called from outside.
  3. After calling the constructor, the factory method, itself async, can call another async method.

This is relevant to our series on Designing, Building & Packaging A Scalable, Testable .NET Open Source Component, and in particular a problem we had when implementing the AmazonS3StorageEngine where some of the Amazon libraries only had async versions.

We can this initialize a Spy asynchronously as follows:

// Create the spy asynchronously
var jamesBond = await v2.Spy.CreateSpy("James", "Bond");
// Use the spy
Console.WriteLine($"Spy was created: {jamesBond.FirstName} {jamesBond.Surname}");

This will print the following:

Playing that song!
Spy was created: James Bond

TLDR

We can get around the fact that constructors cannot be async or call async methods by implementing a factory method with a private constructor to initiailize our objects.

The code is in my GitHub.

Happy hacking!