Where are the generic parameters saved for Async calls? Where to find its name or other information?

The async methods are not that easy.

The C# compiler will generate a comprehensive state machine out of an async method. So the body of the TestClass.Say method will be completely overwritten by the compiler. You can read this great blog post if you want to dive deeper into the async state machinery.

Back to your question.

The compiler will replace the method body with something like this:

<Say>d__0 stateMachine = new <Say>d__0();
stateMachine.<>4__this = this;
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;

<Say>d__0 in this code is a compiler-generated type. It has special characters it its name to prevent you from being able to use this type in your code.

<Say>d__0 is an IAsyncStateMachine implementation. The main logic is contained in its MoveNext method.

It will look similar to this:

TaskAwaiter awaiter;
if (state != 0)
{
    awaiter = HelloWorld.Say<IFoo>().GetAwaiter();
    if (!awaiter.IsCompleted)
    {
        // ...
        builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
        return;
    }
}
else
{
    awaiter = this.awaiter;
    state = -1;
}

awaiter.GetResult();
HelloWorld.Hello<IBar>();

Note that your HelloWorld.Say<IFoo>() call is now here, in this method, not in your original TestClass.Say.

So, to get the generic type information from your method, you will need to inspect the MoveNext state machine method instead of the original TestClass.Say. Search for the call instructions there.

Something like this:

Type asyncStateMachine = 
    typeof(TestClass)
    .GetNestedTypes(BindingFlags.NonPublic)
    .FirstOrDefault(
        t => t.GetCustomAttribute<CompilerGeneratedAttribute>() != null 
        && typeof(IAsyncStateMachine).IsAssignableFrom(t));

MethodInfo method = asyncStateMachine.GetMethod(
    nameof(IAsyncStateMachine.MoveNext),
    BindingFlags.NonPublic | BindingFlags.Instance);

List<MethodInfo> calls = method.GetInstructions()
    .Select(x => x.Operand as MethodInfo)
    .Where(x => x != null)
    .ToList();

// etc

Output:

Void MoveNext()
        System.Threading.Tasks.Task Say[IFoo]()
                ConsoleApp1.IFoo
        System.Runtime.CompilerServices.TaskAwaiter GetAwaiter()
        Boolean get_IsCompleted()
        Void AwaitUnsafeOnCompleted[TaskAwaiter,<Say>d__0](System.Runtime.CompilerServices.TaskAwaiter ByRef, <Say>d__0 ByRef)
                System.Runtime.CompilerServices.TaskAwaiter
                ConsoleApp1.TestClass+<Say>d__0
        Void GetResult()
        Void Hello[IBar]()
                ConsoleApp1.IBar
        Void SetException(System.Exception)
        Void SetResult()

Note that this code depends on current IAsyncStatMachine implementation internals. If the C# compiler changes that internal implementation, this code might break.


You can try getting the generic method info and that way you can find the IFoo generic type argument from this (code taken from the msdn):

private static void DisplayGenericMethodInfo(MethodInfo mi)
    {
        Console.WriteLine("\r\n{0}", mi);

        Console.WriteLine("\tIs this a generic method definition? {0}", 
            mi.IsGenericMethodDefinition);

        Console.WriteLine("\tIs it a generic method? {0}", 
            mi.IsGenericMethod);

        Console.WriteLine("\tDoes it have unassigned generic parameters? {0}", 
            mi.ContainsGenericParameters);

        // If this is a generic method, display its type arguments.
        //
        if (mi.IsGenericMethod)
        {
            Type[] typeArguments = mi.GetGenericArguments();

            Console.WriteLine("\tList type arguments ({0}):", 
                typeArguments.Length);

            foreach (Type tParam in typeArguments)
            {
                // IsGenericParameter is true only for generic type
                // parameters.
                //
                if (tParam.IsGenericParameter)
                {
                    Console.WriteLine("\t\t{0}  parameter position {1}" +
                        "\n\t\t   declaring method: {2}",
                        tParam,
                        tParam.GenericParameterPosition,
                        tParam.DeclaringMethod);
                }
                else
                {
                    Console.WriteLine("\t\t{0}", tParam);
                }
            }
        }
    }