How can I tell if a C# method is async/await via reflection?

In my copy of your code, the MethodInfo for the async method contains the following items in the CustomAttributes property:

  • a DebuggerStepThroughAttribute
  • a AsyncStateMachineAttribute

whereas the MethodInfo for the normal method contains no items in its CustomAttributes property.

It seems like the AsyncStateMachineAttribute should reliably be found on an async method and not on a standard one.

Edit: In fact, that page even has the following in the examples!

As the following example shows, you can determine whether a method is marked with Async (Visual Basic) or async (C# Reference) modifier. In the example, IsAsyncMethod performs the following steps:

  • Obtains a MethodInfo object for the method name by using Type.GetMethod.

  • Obtains a Type object for the attribute by using GetType Operator (Visual Basic) or typeof (C# Reference).

  • Obtains an attribute object for the method and attribute type by using MethodInfo.GetCustomAttribute. If GetCustomAttribute returns Nothing (Visual Basic) or null (C#), the method doesn't contain the attribute.

private static bool IsAsyncMethod(Type classType, string methodName)
{
    // Obtain the method with the specified name.
    MethodInfo method = classType.GetMethod(methodName);

    Type attType = typeof(AsyncStateMachineAttribute);

    // Obtain the custom attribute for the method. 
    // The value returned contains the StateMachineType property. 
    // Null is returned if the attribute isn't present for the method. 
    var attrib = (AsyncStateMachineAttribute)method.GetCustomAttribute(attType);

    return (attrib != null);
}

Damien_The_Unbeliever threw an interesting challenge. I think checking for AsyncStateMachineAttribute isn't a sufficient solution. The original question shouldn't be whether the method is async. Instead it should be whether it's awaitable. Both method samples in Damien's answer will return true if you check for the method GetAwaiter() on the return type. However, only the method marked async will include the AsyncStateMachineAttribute in the custom attributes collection.

Knowing if the method is awaitable is important if you want to use MethodInfo.Invoke() to call the method and you don't know ahead of time if methods that might be registered to a message broker are awaitable.

var isAwaitable = _methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;

object result = null;
if (isAwaitable)
{
    result = await (dynamic)_methodInfo.Invoke(_instance, _parameterArray);
}
else
{
    result = _methodInfo.Invoke(_instance, _parameterArray);
}

EDIT: Good idea to check the return type on MethodInfo. This is my revised code.

var isAwaitable = _methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;

object invokeResult = null;
if (isAwaitable)
{
    if (_methodInfo.ReturnType.IsGenericType)
    {
        invokeResult = (object)await (dynamic)_methodInfo.Invoke(_instance, arguments);
    }
    else
    {
        await (Task)_methodInfo.Invoke(_instance, arguments);
    }
}
else
{
    if (_methodInfo.ReturnType == typeof(void))
    {
        _methodInfo.Invoke(_instance, arguments);
    }
    else
    {
        invokeResult = _methodInfo.Invoke(_instance, arguments);
    }
}

Here's an example of two methods, and I'm asking you why you think that they should be treated differently:

    public static async Task<int> M1(int value)
    {
        await Task.Delay(20000);
        return value;
    }

    public static Task<int> M2(int value)
    {
        return Task.Delay(20000).ContinueWith<int>(_=>value);
    }

They both have, to within a handwave, the exact same runtime behaviour - for 20 seconds they do nothing (and don't hold onto a thread during that period) and then they run a small delegate that just passes back the value that was initially passed to the method.

And yet, you're going to treat one vastly differently from the other because I choose to use the compiler to hide some of the plumbing?