Home > Software engineering >  What are the best practices for implementing a Run method from an interface where synchronous and as
What are the best practices for implementing a Run method from an interface where synchronous and as

Time:10-04

I want to implement an interface that has a Run method:

public interface IRunner
{
    bool Run();
}

But the issue I am facing is that some Run functions should be synchronous and some should be asynchronous. One solution would be to implement two interfaces:

public interface IRunner
{
    bool Run();
}

public interface IRunnerAsync
{
  Task<bool> RunAsync();
}

But the issue here is, that if I have, say, a list of objects implementing these interfaces, I will have to always make a check of what type each object is before calling the method, which feels hacky. What would be best practices to implement interfaces like these?

CodePudding user response:

Perhaps just have the async version, and accept that some callers may implement it internally in a synchronous fashion; they can do this locally and just return the true/false as a wrapped task via Task.FromResult, noting that (at least in current frameworks) the runtime will special-case the bool case, and avoid allocating multiple tasks internally (the details aren't important). Your consumption code can just await an already-completed synchronous result, and everything will keep working. In some very specific scenarios (usually in library code), it may be beneficial for your consumption code to explicitly test for synchronously-completed results, and optimize away some of the async overheads.

In a more general Task<T> scenario, where you don't want the synchronous implementations to have to allocate, consider switching to ValueTask<T> instead; with that API, synchronous implementations can return a new ValueTask<TheType>(value) without allocating, and asynchronous versions can either wrap a Task<T>, or can simply declare themselves as async ValueTask<T> Whatever() and let the compiler worry about the details. There is also a third way of implementing ValueTask<T>, for niche async low-allocation scenarios, but: that's probably overkill here. One caveat with ValueTask[<T>]: a value-task should only be awaited once (or rather: the outcome should only be retrieved once).

  • Related