Skip to content

tommasobertoni/IsAwaitable

Repository files navigation

IsAwaitable

License MIT Nuget netstandard2.0

branch build coverage quality
main CI Coverage CodeFactor Grade
dev CI Coverage CodeFactor Grade

Given an infinite amount of time, everything that can happen will eventually happen... including needing to know at runtime if an object or type can be awaited.

TL;DR

The library provides the following extension methods:

using System.Threading.Tasks;

// Checks if it's awaitable
bool IsAwaitable(this object? instance);
bool IsAwaitable(this Type type);
// await x;

// Check if it's awaitable and returns a result
bool IsAwaitableWithResult(this object? instance);
bool IsAwaitableWithResult(this object? instance, out Type? resultType);
bool IsAwaitableWithResult(this Type type);
bool IsAwaitableWithResult(this Type type, out Type? resultType);
// var foo = await x;

...and some bonus ones:

using IsAwaitable;

// Known awaitables: Task, Task<T>, ValueTask, ValueTask<T>
bool IsKnownAwaitable(this object? instance);
bool IsKnownAwaitable(this Type type);

// Is Task<T> or ValueTask<T>
bool IsKnownAwaitableWithResult(this object? instance);
bool IsKnownAwaitableWithResult(this object? instance, out Type? resultType);
bool IsKnownAwaitableWithResult(this Type type);
bool IsKnownAwaitableWithResult(this Type type, out Type? resultType);

If you want to see how a type, or instance, is compliant with an awaitable expression, you can use the Awaitable type:

using IsAwaitable.Analysis;

_ = Awaitable.Describe("hello");
// null

var description = Awaitable.Describe(typeof(MyCustomAwaitableType));
if (description is not null)
{
    var resultType = description.ResultType;
}

var taskDescription = Awaitable.Describe<Task>();
var isKnownAwaitable = taskDescripti.IsKnownAwaitable;

The Describe function inspects the type to check if it matches the c# language specification for awaitable expressions:

An expression t is awaitable if one of the following holds:

  • t is of compile time type dynamic
  • t has an accessible instance or extension method called GetAwaiter with no parameters and no type parameters, and a return type A for which all of the following hold:
    • A implements the interface INotifyCompletion
    • A has an accessible, readable instance property IsCompleted of type bool
    • A has an accessible instance method GetResult with no parameters and no type parameters

Usage

// On instances
Task doAsync = DoSomethingAsync();
_ = doAsync.IsAwaitable(); // true

// Returing a result
Task<int> promise = GetSomethingAsync();
_ = promise.IsAwaitable(); // true
_ = promise.IsAwaitableWithResult(); // true

// On types
_ = typeof(Task).IsAwaitable(); // true

// On value tasks
_ = typeof(ValueTask).IsAwaitable(); // true
_ = typeof(ValueTask<>).IsAwaitableWithResult(); // true

// On custom awaitables!
class CustomDelay
{
    private readonly TimeSpan _delay;

    public CustomDelay(TimeSpan delay) =>
        _delay = delay;

    public TaskAwaiter GetAwaiter() =>
        Task.Delay(_delay).GetAwaiter();
}

var delay = new CustomDelay(TimeSpan.FromSeconds(2));
_ = delay.IsAwaitable(); // true
_ = delay.IsAwaitableWithResult(); // false

Dynamically await anything

async Task<object> AwaitResultOrReturn(object instance)
{
    return instance.IsAwaitableWithResult()
        ? await (dynamic)instance
        : instance;
}

var foo = GetFoo();
var fooTask = Task.FromResult(foo);

var result1 = await AwaitResultOrReturn(foo);
var result2 = await AwaitResultOrReturn(fooTask);

// foo == result1 == result2

Continuous Integration

github-actions xUnit coverlet coveralls.io codefactor.io

Icon

Created by The Icon Z from The Noun Project.