3

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

11
  • 1
    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? Commented Dec 14, 2016 at 23:04
  • What is your actual question here? The links you've provided show you how to get your desired results. Commented Dec 14, 2016 at 23:04
  • 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. Commented Dec 14, 2016 at 23:07
  • 2
    @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. Commented Dec 14, 2016 at 23:08
  • 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? Commented Dec 14, 2016 at 23:13

2 Answers 2

22

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;
Sign up to request clarification or add additional context in comments.

2 Comments

This looks good. Just when I felt there is no current solution to this, ... could this be.... :) -- testing now.
Isn't there a better place to use the CallerMemberName in the logging process to always include this when an async is used in logging?
1

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)

9 Comments

Are there any caveats (aside from performance), such as memory leaks, or scenarios where this would fail to get the appropriate name ?
@SamuelJackson: Finally a question! Yes. The solution given here relies upon undocumented implementation details subject to change without warning at any time. This can fail at any time for any reason whatsoever. Do not write code that relies for its correctness on the compiler team maintaining this pattern; they have considered changing it in the past and reserve the right to do so in the future.
May be for anonymous async delegate. I don't think any memory leak may occurs we are not holding or binding any object reference.
Can't agree more @EricLippert!! which is why I said its a 'hack'.
I suppose probably the cheesiest way is to revert to old school. Inside each method define a variable and set it's value to the name of the Method it is inside. Was really hoping with all these "advancements" that there would be something similar to other languages - in php Magic Constants,
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.