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);
}
}
}
}