Why isn't there an IAwaitable and IAwaiter interface

Viewed 723

I recently learned about the possibility to have custom awaitable types and as this question and Stephen Toub states, there are several requirements to be an awaitable type.

So if a type T wants to be awaitable it must

  • expose an parameterless method GetAwaiter that returns a valid awaiter

and if a type A wants to be a valid awaiter it must

  • Implement the INotifyCompletion interface
  • Provide a boolean property called IsCompleted
  • Provide a parameterless GetResult method that returns void or TResult

So now I'm asking if all that is required to be an awaitable type, why isn't that part of some interfaces like

public interface INotifyCompletion
{
    bool IsCompleted { get; }
    void OnCompleted(Action continuation);
}

public interface IAwaiter : INotifyCompletion
{
    void GetResult();
}

public interface IAwaitable<TAwaiter> where TAwaiter : IAwaiter
{
    TAwaiter GetAwaiter();
}

public interface IAwaiter<TResult> : INotifyCompletion
{
    TResult GetResult();
}

// this would probably not necessary but would likely help to identify
// awaitables that return a value
public interface IAwaitable<TAwaiter, TResult> where TAwaiter : IAwaiter<TResult>
{
    TAwaiter GetAwaiter();
}

I also do understand that the compiler does not need it, since it can check all that at compile time without any penalty. But since there is the INotifyCompletion interface for the OnCompleted() method, why isn't the rest of the interface for awaitables and awaiters packed in some interfaces?

This would most likely help to inform programmers how to implement this.

I also know that one can make an type awaitable by providing an extension method that return a valid awaiter, but again why isn't the whole interface for an awaiter packed in a single interface but has holes (i.e. the IsCompleted property is not part of any interface but required)?

3 Answers

IAwaiter and IAwaitable interfaces are available if you want to use them.

Note that they are not mandatory to use, but if you feel like they will make your life easier - feel free to use them.

This blog post may be worth a read as well.

As mijwlls said before, interfaces are available if you want them. But there is a very important reason for the compiler to not require the implementation of a specific interface.

If you implement an interface on a struct. And then return that struct as an instance of the interface. It gets boxed and allocated on the heap. Same with passing it as an parameter if the type of the parameter is the interface itself. This goes for straight up casting to. Boxing costs time and increases pressure on the Garbage collector.

If the compiler required people to implement the IAwaitable interface instead of directly checking the types for members, then it would be impossible to implement something like ValueTask.

Because the return value of GetAwaiter would have to be typed as an interface. Which necessitates allocating it on the heap.

Note that the same applies to the IEnumerator and IEnumerable interfaces too. You don't actually have to implement IEnumerable on a struct or class to use it in a foreach loop. Your type just has to have a parameterless method that returns an instance of a type that has a "Current" property and a parameterless "MoveNext" method that returns a boolean.

I guess because it would violate the semantic. The async/await is a compile-time feature which implies that the compiler should be aware of what type of awaiter to use in a generated state machine which can be impossible with some interface usage scenarios implying runtime polymorphism when actual type of variable cannot be determined during the compilation. So essentially it means that not all cases can be covered with interface-based awaiters.

Related