2
votes

Following is a tiny snippet of code I wrote to demonstrate the basics of this problem.

Code

private async void Form1_Load( object sender, EventArgs e ) {
    var result = await TestAsyncMethodName();
}

private async Task<string> TestAsyncMethodName() {
    string name = "Method: " + System.Reflection.MethodBase.GetCurrentMethod().Name;

    int x = 0;
    foreach ( StackFrame sf in (new StackTrace()).GetFrames()) {
        x++;
        name = name + "\nStack Frame [" + x + "] : " + sf.GetMethod().Name;
    }

    await Task.Run( () => { name = name + "\nAnonymous Method: " + System.Reflection.MethodBase.GetCurrentMethod().Name; } );

    return name;
}

Result

Method: MoveNext
Stack Frame [1] : MoveNext
Stack Frame [2] : Start
Stack Frame [3] : TestAsyncMethodName
Stack Frame [4] : MoveNext
Stack Frame [5] : Start
Stack Frame [6] : Form1_Load
Stack Frame [7] : OnLoad
Stack Frame [8] : OnCreateControl
Stack Frame [9] : CreateControl
Stack Frame [10] : CreateControl
Stack Frame [11] : WmShowWindow
Stack Frame [12] : WndProc
Stack Frame [13] : WndProc
Stack Frame [14] : WmShowWindow
Stack Frame [15] : WndProc
Stack Frame [16] : OnMessage
Stack Frame [17] : WndProc
Stack Frame [18] : DebuggableCallback
Stack Frame [19] : ShowWindow
Stack Frame [20] : SetVisibleCore
Stack Frame [21] : SetVisibleCore
Stack Frame [22] : set_Visible
Stack Frame [23] : RunMessageLoopInner
Stack Frame [24] : RunMessageLoop
Stack Frame [25] : Run
Stack Frame [26] : Main
Stack Frame [27] : _nExecuteAssembly
Stack Frame [28] : ExecuteAssembly
Stack Frame [29] : RunUsersAssembly
Stack Frame [30] : ThreadStart_Context
Stack Frame [31] : RunInternal
Stack Frame [32] : Run
Stack Frame [33] : Run
Stack Frame [34] : ThreadStart
Anonymous Method: <TestAsyncMethodName>b__0

Expected Result

Method: TestAsyncMethodName
Stack Frame [1] : MoveNext
Stack Frame [2] : Start
Stack Frame [3] : TestAsyncMethodName
Stack Frame [4] : MoveNext
Stack Frame [5] : Start
Stack Frame [6] : Form1_Load
Stack Frame [7] : OnLoad
Stack Frame [8] : OnCreateControl
Stack Frame [9] : CreateControl
Stack Frame [10] : CreateControl
Stack Frame [11] : WmShowWindow
Stack Frame [12] : WndProc
Stack Frame [13] : WndProc
Stack Frame [14] : WmShowWindow
Stack Frame [15] : WndProc
Stack Frame [16] : OnMessage
Stack Frame [17] : WndProc
Stack Frame [18] : DebuggableCallback
Stack Frame [19] : ShowWindow
Stack Frame [20] : SetVisibleCore
Stack Frame [21] : SetVisibleCore
Stack Frame [22] : set_Visible
Stack Frame [23] : RunMessageLoopInner
Stack Frame [24] : RunMessageLoop
Stack Frame [25] : Run
Stack Frame [26] : Main
Stack Frame [27] : _nExecuteAssembly
Stack Frame [28] : ExecuteAssembly
Stack Frame [29] : RunUsersAssembly
Stack Frame [30] : ThreadStart_Context
Stack Frame [31] : RunInternal
Stack Frame [32] : Run
Stack Frame [33] : Run
Stack Frame [34] : ThreadStart
Anonymous Method: <TestAsyncMethodName>b__0

References (Research done):

StackOverflow

Microsoft

2
So what's your question? You wrote some code, you expected it to do something, you were wrong. What's your specific question about code?Eric Lippert
What is your actual question here? The links you've provided show you how to get your desired results.Rob
Wrong, the question is a problem, not really a question. The commands are working as they are intended, but do not return the method name when just using Reflection. Please read the output carefully. Inside an async method, reflection returns "MoveNext" not the method name.Kraang Prime
@SamuelJackson: That's the correct behaviour, so I'm not sure how to help you. This is a question-and-answer site. Ask a question if you want an answer.Eric Lippert
It might be helpful for you to consider a similar problem. Suppose you have IEnumerable<int> Foo() { ... yield return 1; ... } The code inside the block does not run when Foo is called. It does not run until MoveNext is called on the iterator. If you fetched the name of the current method inside that block, what would you expect, Foo, a function which has already returned, or MoveNext, the function you called to activate the iterator block code?Eric Lippert

2 Answers

21
votes

Change

string name = "Method: " + System.Reflection.MethodBase.GetCurrentMethod().Name;

to

string name = "Method: " + GetActualAsyncMethodName();

Then implement that method as:

static string GetActualAsyncMethodName([CallerMemberName]string name = null) => name;
1
votes

When you use Async your code is converted into a statemachine and so the stack traces and run time information reflects the generated code. If open your assembly where code is in ILSPLY. You method would look like this after generation of statemachine:

    .class nested private auto ansi sealed beforefieldinit '<TestAsyncMethodName>d__2'
    extends [mscorlib]System.Object
    implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Fields
    .field public int32 '<>1__state'
    .field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> '<>t__builder'
    .field public class WindowsFormsApp_Recipients.TestForm '<>4__this'
    .field private class WindowsFormsApp_Recipients.TestForm/'<>c__DisplayClass2_0' '<>8__1'
    .field private int32 '<x>5__2'
    .field private class [mscorlib]System.Diagnostics.StackFrame[] '<>s__3'
    .field private int32 '<>s__4'
    .field private class [mscorlib]System.Diagnostics.StackFrame '<sf>5__5'
    .field private valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter '<>u__1'

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x279e
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method '<TestAsyncMethodName>d__2'::.ctor

    .method private final hidebysig newslot virtual 
        instance void MoveNext () cil managed 
    {
        .override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
        // Method begins at RVA 0x28b0
        // Code size 445 (0x1bd)
        .maxstack 5
        .locals init (
       .................
       .................
       More IL CODE HERE...

As you can see you have a new class type <TestAsyncMethodName>d__2 which wrap the actual logic of your method in generated method MoveNext. So when you ask for System.Reflection.MethodBase.GetCurrentMethod().Name it will give you MoveNext instead of your actual method name.

To get the correct result you can put a hack here:

var regex = new Regex(@"<(\w+)>.*");
string name = "Method: " + regex.Match(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name).Groups[1].Value;

Because the state machine code generation generates a type for the Method name marked as async this Hack will work fine.

The least expensive, and simplest workaround to use at the moment, would be to declare a string variable which acts as a container for the name and then call this as required. And example of this follows :

public async Task<string> Foo() {
     string __FUNCTION__ = "Foo";

     // await / etc code here
}

Another method which is less expensive (memory) than strings, would be to create an enum for your methods and convert that to a string. This would be less expensive in memory when setting the method name, but slightly more expensive when converting back to a string for use. An example of this follows :

public class MyClass {

   public enum __FUNCTIONS {
        Foo,
        Bar
   }

   public async Task<string> Foo() {
        __FUNCTIONS __FUNCTION__ = __FUNCTIONS.Foo;

        // await / etc code here
   }

   public async Task<string> Bar() {
        __FUNCTIONS __FUNCTION__ = __FUNCTIONS.Bar;

        // await / etc code here
   }

}

Unfortunately, there is no real magic constant support in Visual Studio as of this date, and it doesn't appear to be something that will happen in the future as long as the language is a JIT IL, as this technology seems limited to what can be provided through reflection instead of code that appears to be aware of itself (like PHP's __FUNCTION__, etc)