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
- Get Method name that threw exception
- Can you use reflection to find the name of the currently executing method?
- How to get the name of the current method from code [duplicate]
Microsoft
- MethodBase.GetCurrentMethod() Method
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;
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)