【问题标题】:Get current method name from async function?从异步函数获取当前方法名称?
【发布时间】:2013-11-23 05:22:29
【问题描述】:

是否可以从异步函数中获取当前方法名称?

我试过了:

System.Reflection.MethodInfo.GetCurrentMethod();

我已经尝试使用 StackTrace 和 StrackFrame,如下所示:

StackTrace strackTrace = new StackTrace();

for (int i = 0; i < strackTrace.GetFrames().Length; i++)
{
    SafeNativeMethods.EtwTraceInfo("Function" + i + ":" + function);
    SafeNativeMethods.EtwTraceInfo("Type" + i + ":" + strackTrace.GetFrame(i).GetType().Name);
    SafeNativeMethods.EtwTraceInfo("Method" + i + ":" + strackTrace.GetFrame(i).GetMethod().Name);
    SafeNativeMethods.EtwTraceInfo("ToString Call" + i + ":" + strackTrace.GetFrame(i).ToString());
}

但它们似乎都不起作用,我会得到“.ctor”、“InvokeMethod”、“Invoke”、“CreateInstance”、“CreateKnownObject”或“CreateUnknownObject”或“MoveNext”

关于如何做到这一点的任何想法?我想创建一个通用的logger函数,又不想传入调用logger函数的函数名,所以尝试了stacktrace的方法,没用。

我放弃了,说,好吧,我将函数名作为第一个参数传入,但是当我从调用通用记录器函数的调用函数中调用反射方法时,我总是得到“.ctor” "

有什么想法吗?请注意,我正在调用的通用记录器函数是同一类中的静态方法(现在必须是这种方式......)。

【问题讨论】:

标签: c# async-await


【解决方案1】:

C# 5 添加了调用者信息属性,可以为您提供更多您正在寻找的内容。请注意,这些在编译时将适当的信息插入调用站点,而不是使用运行时信息。功能更有限(显然,您无法获得完整的调用堆栈),但速度要快得多。

使用 CallerMemberNameAttribute 的示例:

using System.Runtime.CompilerServices;

public static void Main(string[] args)
{
    Test().Wait();            
}

private static async Task Test()
{
    await Task.Yield();
    Log();
    await Task.Yield();
}

private static void Log([CallerMemberName]string name = "")
{
    Console.WriteLine("Log: {0}", name);
}

还有 CallerFilePath 和 CallerLineNumber 属性可以获取有关呼叫站点的其他信息。

【讨论】:

  • 不幸的是,我现在无法针对 C# 5.0 进行编译(尽管我会再次检查)。有没有想到的其他解决方案?
  • @user2939634 那么你是如何用async 编译的呢?您是否使用 .Net 4.0 的异步目标包?如果是这样,您可以在 System.Runtime.CompilerServices 命名空间中定义自己的属性 CallerMemberName 并且它应该可以工作(正如 svick 在 cmets 中提到的另一个答案)。
  • 使用调用者信息属性获取方法名称在全局错误记录器中是没有用的,因为您无法获取引发异常的调用堆栈中的第一个方法。 [CallerMemberName] 仅获取调用全局错误处理程序的方法的名称。
【解决方案2】:

此方法适用于 async 方法调用以及普通方法。 (C#5)

/// <summary>
///     Returns Current method name
/// </summary>
/// <returns>callers method name</returns>
public string GetCurrentMethod([CallerMemberName] string callerName = "")
{
    return callerName;
}

【讨论】:

    【解决方案3】:

    您可以使用CallerMemberNameAttribute,这是在.NET 中添加的Caller Information 属性之一,而不是手动进行堆栈帧遍历,这既昂贵又冒险(因为发布版本可能会优化某些方法) 4.5(如果您使用 async/await,则您已经使用过)用于这种确切的场景 - 为记录器、属性更改的处理程序等传递成员名称。

    是这样的:

    public void LogMessage(string message, [CallerMemberName] string caller = "")
    { 
        // caller should contain the name of the method that called LogMessage.
    }
    

    我不知道 async 方法有什么限制。

    【讨论】:

    • 调用者成员属性并不是 .Net 4.5 的真正特性,它们是 C# 5.0 的特性。如果您使用 C# 5.0 来定位 .Net 4.0,您可以自己定义该属性,它会起作用。
    • @svick 你能发一个例子或者给我们一个链接吗?
    • @StefanVasiljevic This code 为我工作。
    • @svick 非常感谢!我将在日志记录和 Rising PropertyChanged 事件时使用它。你知道这种方法有什么奇怪的问题吗?
    【解决方案4】:

    我混合使用了 Nathan 和 Mike 的答案。

    使用GetType() 从堆栈跟踪中查询方法对我不起作用。不保证。但是使用CallerMemberNameAttrinute 可以让我通过名称获得确切的方法。

    所以我的代码是:

    using System.Runtime.CompilerServices;
    
    public static void Main(string[] args)
    {
        Test().Wait();            
    }
    
    private static async Task Test()
    {
        await Task.Yield();
        Log();
        await Task.Yield();
    }
    
    private static void Log([CallerMemberName]string methodName = "")
    {
       var method = new StackTrace()
           .GetFrames()
           .Select(frame => frame.GetMethod())
           .FirstOrDefault(item => item.Name == methodName);
    
       Console.WriteLine("Log: {0}", method.DeclaringType + "." + method.Name);
    }
    

    这样我可以得到方法名称及其完整的命名空间路径。

    【讨论】:

      【解决方案5】:

      您需要在异步方法的早期捕获方法名称,在第一次异步调用之前的某个位置。我发现跳过编译器生成的状态机最方便的方法是查看堆栈跟踪中每个方法的声明类型。

      var method = new StackTrace()
          .GetFrames()
          .Select(frame => frame.GetMethod())
          .FirstOrDefault(item => item.DeclaringType == GetType());
      await Task.Yield();
      if (method != null)
      {
          Console.WriteLine(method.Name);
      }
      

      【讨论】:

        【解决方案6】:

        我最近在我的日志消息中遇到了同样的问题,这里的答案很有帮助。我正在分享我的代码,以防它可以帮助其他人解决他们的问题。

        using System.Runtime.CompilerServices;
        
        namespace Foo.Services
        {
            public interface ILogFormatter
            {
                public string FormatMessage(LogType logType, string details, [CallerMemberName] string caller = "");
            }
        
        
            public enum LogType
            {
                Debug,
                Information,
                Warning,
                Error
            }
        
            public class LogFormatterOptions
            {
                public string Subject { get; set; }
            }
        
            public class LogFormatter : ILogFormatter
            {
                public LogFormatter(LogFormatterOptions options)
                {
                    Subject = options.Subject;
                }
        
                public string Subject { get; set; }
        
                public string FormatMessage(LogType logType, string details, [CallerMemberName] string caller = "")
                {
                    return $"{Subject} - {logType}: caller: {caller} - {details}";
                }
            }
        }
        

        这就是该方法的调用方式,它适用于异步和同步方法。请注意,我没有为调用者输入传递值,而是获取调用者方法名称。

         logger.LogInformation(logFormatter.FormatMessage(LogType.Information, ""));
        

        【讨论】:

          【解决方案7】:
          1. 使用 System.Runtime.CompilerServices;

          2. 在静态类文件 (ExtensionHelper) 中创建静态方法 (GetActualAsyncMethodName)。

          公共静态类 ExtensionHelper { 公共静态字符串 GetActualAsyncMethodName([CallerMemberName] string name = null) => name; }

          1. 调用 GetActualAsyncMethodName() 以获取异步方法名称。

            字符串方法名 = ExtensionHelper.GetActualAsyncMethodName();

          【讨论】:

            【解决方案8】:

            我想现在回答你的问题有点晚了,但由于我遇到了同样的问题,并且由于互联网上没有很好的解决方案,我需要自己解决这个问题,这就是我所做的和它至少对我来说完美 -

            1. 在所有调用者都可以访问的地方定义以下Regex,因为Regex对象需要在使用它们之前进行解析;因此,它可能很昂贵,而且如果我们可以使用相同的,一遍又一遍地创建它们不是一个好习惯。
              public static readonly Regex ASYNC_METHOD_NAME_FORMAT = new Regex(@"^\&lt;(?\w+)&gt;\w+?");
            2. 使用以下代码获取方法名
              string methodName = ASYNC_METHOD_NAME_FORMAT.Match(MethodBase.GetCurrentMethod().ReflectedType.Name).Groups["method_name"].Value

            我希望这会有所帮助!

            【讨论】:

              【解决方案9】:

              您可以将 System.Reflection.MethodBase 对象 strackTrace.GetFrame(i).GetMethod() 发送到检查 System.Reflection.MethodBase.DeclaringType.FullName 是否具有 字符的函数,如果有的话将获得位于 <...> 字符之间的请求方法名称。

              static private string getMethodName(System.Reflection.MethodBase method)
                  {
                      string _methodName = method.DeclaringType.FullName;
              
                      if (_methodName.Contains(">") || _methodName.Contains("<"))
                      {
                          _methodName = _methodName.Split('<', '>')[1];
                      }
                      else
                      {
                          _methodName = method.Name;
                      }
              
                      return _methodName;
                  }
              

              以及如何使用的例子是:

              var _stackTrace = new System.Diagnostics.StackTrace(exception, true);
              string _methodName = getMethodName(_stackTrace.GetFrame(0).GetMethod());
              

              【讨论】:

                猜你喜欢
                • 2016-03-13
                • 2014-01-15
                • 2020-01-11
                • 2011-01-04
                • 2011-04-17
                • 2011-06-11
                • 2021-02-09
                • 2010-10-01
                • 1970-01-01
                相关资源
                最近更新 更多