Using async Tasks with the builder pattern

As I said in comments, you could write Extension method for HomeViewModelBuilder as well as Task<HomeViewModelBuilder> and chain it.

public static class HomeViewModelBuilderExtension
{
    public static Task<HomeViewModelBuilder> WithCarousel(this HomeViewModelBuilder antecedent)
    {
        return WithCarousel(Task.FromResult(antecedent));
    }

    public static async Task<HomeViewModelBuilder> WithCarousel(this Task<HomeViewModelBuilder> antecedent)
    {
        var builder = await antecedent;
        var carouselItems = await builder.Service.GetAsync();
        builder.ViewModel.Carousel = carouselItems;
        return builder;
    }

    public static Task<HomeViewModelBuilder> WithFeaturedItems(this HomeViewModelBuilder antecedent, int number)
    {
        return WithFeaturedItems(Task.FromResult(antecedent), number);
    }

    public static async Task<HomeViewModelBuilder> WithFeaturedItems(this Task<HomeViewModelBuilder> antecedent, int number)
    {
        var builder = await antecedent;
        builder.ViewModel.FeaturedItems = number;
        return builder;
    }
}

We're adding couple of methods for single operation so that you can chain it with HomeViewModelBuilder or Task<HomeViewModelBuilder>. Otherwise you'll not be able to call builder.WithCarousel()

Then use it like

private static void Main()
{
    HomeViewModelBuilder builder = new HomeViewModelBuilder();
    var task = builder
        .WithCarousel()
        .WithFeaturedItems(3);        
}

With the builder pattern you can create a strategy of building the object. It does not construct the object until the build method is called. If the logic to populate the object is in the build method then you can call all of the async methods together.

See example code for your builder below. Its just a demonstration of the concept so you may want to improve on it.

    public class Builder
    {
        private bool hasCarousel = false;
        private int featuredItems = 0;

        public Builder WithCarousel()
        {
            hasCarousel = true;
            return this;
        }

        public Builder WithFeaturedItems(int featured)
        {
            featuredItems = featured;
            return this;
        }

        public BuiltObject Build()
        {
            if (hasCarousel)
            {
                // do carousel related calls
            }
            if (featuredItems > 0)
            {
                // do featured items related calls.
            }

            // build and return the actual object.
        }
    }

The deal with async, is that it has the ripple effect. It tends to spread through your code to keep it async "async all the way".

If you want to allow for the builder pattern (or any other fluent pattern, like LINQ) while keeping it async you need to have an async overload for each of the possible calls you want to make. Otherwise you can't use them, or will use them wrong (with "sync over async" for example).

async-await is fairly new, but I'm sure over time you will have an async option for almost anything.


I have not actually done this before, but here's an alternative to Sriram's solution.

The idea is to capture the tasks in the builder object instead of the result of the tasks. The Build method then waits for them to complete and returns the constructed object.

public sealed class HomeViewModelBuilder
{
  // Example async
  private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
  public HomeViewModelBuilder WithCarousel()
  {
    _carouselTask = _service.GetAsync();
    return this;
  }

  // Example sync
  private int _featuredItems;
  public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
  {
    _featuredItems = featuredItems;
    return this;
  }

  public async Task<HomeViewModel> BuildAsync()
  {
    return new HomeViewModel(await _carouselTask, _featuredItems);
  }
}

Usage:

var viewModel = await builder
    .WithCarousel(),
    .WithFeaturedItems(3),
    .BuildAsync();

This builder pattern works with any numbers of asynchronous or synchronous methods, for example:

public sealed class HomeViewModelBuilder
{
  private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
  public HomeViewModelBuilder WithCarousel()
  {
    _carouselTask = _service.GetAsync();
    return this;
  }

  private Task<int> _featuredItemsTask;
  public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
  {
    _featuredItemsTask = _featuredService.GetAsync(featuredItems);
    return this;
  }

  public async Task<HomeViewModel> BuildAsync()
  {
    return new HomeViewModel(await _carouselTask, await _featuredItemsTask);
  }
}

Usage is still the same.