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

时间:2016-12-14 22:53:13

标签: c# asynchronous reflection

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

代码

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;
}

结果

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

预期结果

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

参考文献(研究完成):

的StackOverflow

微软

2 个答案:

答案 0 :(得分:7)

更改

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

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

然后将该方法实现为:

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

答案 1 :(得分:1)

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

    .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...

正如您所看到的,您有一个新类类型<TestAsyncMethodName>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<string> Foo() {
     string __FUNCTION__ = "Foo";

     // await / etc code here
}

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

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
   }

}

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