I was working on developing a Fluent Builder and encountered the need to incorporate asynchronous methods without disrupting the chaining. How can I address this?
A fictional example to illustrate the problem
public class EmailBuilder
{
private readonly Email email;
public EmailBuilder From(string from)
{
email.From = from;
return this;
}
public async Task<EmailBuilder> To(string to)
{
if(await VerifyAsync(to))
{
email.To = to;
}
return this;
}
public EmailBuilder Body(string body)
{
email.Body = body;
return this;
}
}
The call would look like this:
ms.SendEmail(email => email
.From("[email protected]")
.To("[email protected]")
.Body("Hello, how are you?"));
My answer to this question kept nagging at the back of my brain, so I gave it more thought and decided to address the actual question more directly. While I personally advocate for separation of concerns and believe that the example provided above would be better handled by having the validation, synchronous or not, be provided in a separate class (probably the smtp manager), I nonetheless concede that there exist use cases where it is desirable to call a Task method from a synchronous method. With that in mind, here is a more direct approach that can be used as a general rule with minimal deadlock risk.
I would also point out that it would make more sense in this specific example to have your verifier throw an exception which should be handled by the invoker of either the To method or the Verify method. Especially in the case of the To method invoked in a fluent chain, failing silently would obviate the whole point of calling Verify.
I hope this answer more directly addresses your question.