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

Community
  • 1
  • 1
Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
  • 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*? – Eric Lippert Dec 14 '16 at 23:04
  • What is your actual question here? The links you've provided show you how to get your desired results. – Rob Dec 14 '16 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. – Kraang Prime Dec 14 '16 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. – Eric Lippert Dec 14 '16 at 23:08
  • It might be helpful for you to consider a similar problem. Suppose you have `IEnumerable 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 Dec 14 '16 at 23:13
  • Also this is a duplicate of http://stackoverflow.com/questions/20158902/get-current-method-name-from-async-function – Eric Lippert Dec 14 '16 at 23:18
  • and http://stackoverflow.com/questions/22598323/movenext-instead-of-actual-method-task-name – Eric Lippert Dec 14 '16 at 23:19
  • and http://stackoverflow.com/questions/28022799/methodbase-object-of-async-function – Eric Lippert Dec 14 '16 at 23:19
  • @EricLippert - Addressing your alleged "duplicates" [1], there is no working solution provided. [2] claims it is not possible which my stack dumps above show the name is there so this is a lie - unacceptable !. [3] are you just searching for "async" as that entire page has NO relevance whatsoever. They are having an issue invoking code from within an async method. – Kraang Prime Dec 15 '16 at 01:23
  • @EricLippert - regarding your 'flow' description, I understand completely where these abstract values are coming from -- parts of the code which I do not control or even see at the IL abstract through JIT. I don't care about any of that -- I only care about my code, not how the compiler (or pseudo "compiler" interpret / converge / etc. If I did care, I would be writing ASM -- in which case any wonky business would be to hardware abstractions, not a funky middle-man --- and not a low level language which has been thrust upon me. – Kraang Prime Dec 15 '16 at 01:26
  • Possible duplicate of [Get current method name from async function?](https://stackoverflow.com/questions/20158902/get-current-method-name-from-async-function) – GSerg Sep 25 '19 at 14:54

2 Answers2

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;
John Rasch
  • 62,489
  • 19
  • 106
  • 139
  • This looks good. Just when I felt there is no current solution to this, ... could this be.... :) -- testing now. – Kraang Prime Dec 15 '16 at 17:51
  • 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? – Jeroen Dec 01 '17 at 10:23
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)

Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
vendettamit
  • 14,315
  • 2
  • 32
  • 54
  • Are there any caveats (aside from performance), such as memory leaks, or scenarios where this would fail to get the appropriate name ? – Kraang Prime Dec 14 '16 at 23:55
  • 1
    @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. – Eric Lippert Dec 15 '16 at 00:02
  • May be for anonymous async delegate. I don't think any memory leak may occurs we are not holding or binding any object reference. – vendettamit Dec 15 '16 at 00:03
  • 1
    Can't agree more @EricLippert!! which is why I said its a 'hack'. – vendettamit Dec 15 '16 at 00:05
  • 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](http://php.net/manual/en/language.constants.predefined.php), – Kraang Prime Dec 15 '16 at 01:14
  • I am upvoting this because it is kind of a work-around to obtaining it, however it is much more writing than simply setting `string name = "MyCoolFunction";` and has no real viability for re-use due to unreliable in nature, and increased complexity and instability if wrapped within a reusable method to be called later. I think my hack may be a bit less expensive where the method name is exposed during execution of a sub-task that is awaited within the initial async method as this tends to expose the name in fewer reflection calls – Kraang Prime Dec 15 '16 at 01:32
  • If you don't mind, I would like to append this concept to your solution making it more rounded -- as well as an example of the classic `string _function = "myfunctionname";` option since that is clearly the most reliable and least expensive - sadly. – Kraang Prime Dec 15 '16 at 01:34
  • That's what I miss in C# the Aspect oriented programming. You could use an attribute for the sake of reusability in such case. – vendettamit Dec 15 '16 at 01:45
  • That is true. I considered a property, but I think there would be points where it would conflict if two processes were running on separate threads within the same class instance. I am unsure, but it just 'feels' like setting up for something to break :) Attributes on each method can work as well, but to access would still require reflection. :S – Kraang Prime Dec 15 '16 at 02:08