Home > Enterprise >  Func<Task> disguised as Action
Func<Task> disguised as Action

Time:10-11

The problem

I have the following code:

Action function = async () => await Task.CompletedTask;

The problem with this code is that even though technically speaking it is an async delegate (one that can be awaited) I cannot await it. In other words, the above could be written equivalently as

Func<Task> function = async () => await Task.CompletedTask;

The latter can be awaited, while the former cannot, even though this is the same method internally. Obviously, this is a contrived example, in my use case more complex code resides in a more complex codebase and it is not easy to spot all misusages.

The question

Can I check the actual type of function at runtime and cast it to the correct one?

What I tried

Code below only throws if I declare function as Func<Task> (aka does not work as I expect it to)

Action function = async () => await Task.CompletedTask;
if (function.GetMethodInfo().ReturnType.IsEquivalentTo(typeof(Task)))
{
    throw new Exception();
}

Code below does not even compile

Action function = async () => await Task.CompletedTask;
if (function is Func<Task> func)
{
    
}

CodePudding user response:

even though this is the same method internally.

It's not, though.

The delegate is compiled into a method, sure, but the type of that method is determined by what it's assigned to. If it's assigned to Func<Task>, then it's a Task-returning method. If it's assigned to Action, then it's a void-returning method.

So, check the actual type of function at runtime isn't going to work; runtime is too late.

Instead, you need a compile-time check. And good news: there are analyzers that should catch this. E.g., ReSharper or Roslyn analyzers.

CodePudding user response:

Checking the type of the delegate at runtime

Does calling .GetType() help?

Code below does not even compile

Action function = async () => await Task.CompletedTask;
if (function is Func<Task> func)
{
   
}

This will compile and run

Action function = async () => await Task.CompletedTask;
if (function.GetType() == typeof(Func<Task>))
{
    // not reachable
}

At runtime

Action function1 = async () => await Task.CompletedTask;
Func<Task> function2 = async () => await Task.CompletedTask;

var b = function1.GetType() == typeof(Action); // true
b = function1.GetType() == typeof(Func<Task>); // false
b = function2.GetType() == typeof(Action); // false
b = function2.GetType() == typeof(Func<Task>); // true

Some Useful Extensions

public static bool IsAction(this MulticastDelegate @delegate) =>
    @delegate.GetType() == typeof(Action);

public static bool ReturnsTask(this MulticastDelegate @delegate) =>
    @delegate.GetType() == typeof(Func<Task>);

public static bool ReturnsTask<T>(this MulticastDelegate @delegate) =>
    @delegate.GetType() == typeof(Func<Task<T>>);

Example Usage

var function = async () => await Task.FromResult(true);
var b = function.IsAction(); // false
b = function.ReturnsTask(); // false
b = function.ReturnsTask<string>(); // false
b = function.ReturnsTask<bool>(); // true
if (b)
{
    // possible to await
    await function?.Invoke();
}
  • Related