使用reflection在异步方法中获取方法名称不会返回预期结果

以下是我编写的一小段代码,用于演示此问题的基础知识。

private async void Form1_Load( object sender, EventArgs e ) { var result = await TestAsyncMethodName(); } private async Task 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; } 

结果

 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: b__0 

预期结果

 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: b__0 

参考文献(研究完成):

堆栈溢出

  • 获取引发exception的方法名称
  • 您可以使用reflection来查找当前正在执行的方法的名称吗?
  • 如何从代码中获取当前方法的名称[复制]

微软

  • MethodBase.GetCurrentMethod()方法

更改

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

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

然后将该方法实现为:

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

使用Async您的代码将转换为状态机,因此堆栈跟踪和运行时信息反映了生成的代码。 如果打开代码在ILSPLY中的程序集。 生成状态机后,您的方法看起来像这样:

  .class nested private auto ansi sealed beforefieldinit '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 '<>t__builder' .field public class WindowsFormsApp_Recipients.TestForm '<>4__this' .field private class WindowsFormsApp_Recipients.TestForm/'<>c__DisplayClass2_0' '<>8__1' .field private int32 '5__2' .field private class [mscorlib]System.Diagnostics.StackFrame[] '<>s__3' .field private int32 '<>s__4' .field private class [mscorlib]System.Diagnostics.StackFrame '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 '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... 

如您所见,您有一个新类类型d__2 ,它在生成的方法MoveNext包装方法的实际逻辑。 所以,当你要求System.Reflection.MethodBase.GetCurrentMethod().Name它会给你MoveNext而不是你的实际方法名。

要获得正确的结果,您可以在这里进行攻击:

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

因为状态机代码生成生成一个标记为异步的方法名称的类型,所以这个哈克工作正常。

目前使用的最便宜,最简单的解决方法是声明一个字符串变量,该变量充当名称的容器,然后根据需要调用它。 例如:

 public async Task Foo() { string __FUNCTION__ = "Foo"; // await / etc code here } 

另一种比字符串更便宜(内存)的方法是为您的方法创建一个枚举并将其转换为字符串。 在设置方法名称时,这在内存中会更便宜,但在转换回字符串以供使用时会稍微昂贵一些。 一个例子如下:

 public class MyClass { public enum __FUNCTIONS { Foo, Bar } public async Task Foo() { __FUNCTIONS __FUNCTION__ = __FUNCTIONS.Foo; // await / etc code here } public async Task Bar() { __FUNCTIONS __FUNCTION__ = __FUNCTIONS.Bar; // await / etc code here } } 

不幸的是,到目前为止,Visual Studio中没有真正的魔术常量支持,只要该语言是JIT IL,它似乎不会在将来发生,因为这项技术似乎仅限于通过reflection提供,而不是看起来自己知道的代码(如PHP的__FUNCTION__等)