登录 C# 时,如何知道调用当前方法的方法的名称?我对 System.Reflection.MethodBase.GetCurrentMethod()
了如指掌,但我想在堆栈跟踪中更进一步。我考虑过解析堆栈跟踪,但我希望找到一种更清晰、更明确的方式,例如 Assembly.GetCallingAssembly()
,但用于方法。
StackTrace
、StackFrame
和 CallerMemberName
)的快速 BenchmarkDotNet 基准测试,并将结果发布为其他人在此处查看的要点:gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
尝试这个:
using System.Diagnostics;
// Get call stack
StackTrace stackTrace = new StackTrace();
// Get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);
单线:
(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name
它来自 Get Calling Method using Reflection [C#]。
在 C# 5 中,您可以使用 caller info 获取该信息:
//using System.Runtime.CompilerServices;
public void SendError(string Message, [CallerMemberName] string callerName = "")
{
Console.WriteLine(callerName + "called me.");
}
您还可以获得 [CallerFilePath]
和 [CallerLineNumber]
。
[CallerTypeName]
已从当前的 .Net 框架(4.6.2)和 Core CLR 中删除
您可以使用呼叫者信息和可选参数:
public static string WhoseThere([CallerMemberName] string memberName = "")
{
return memberName;
}
该测试说明了这一点:
[Test]
public void Should_get_name_of_calling_method()
{
var methodName = CachingHelpers.WhoseThere();
Assert.That(methodName, Is.EqualTo("Should_get_name_of_calling_method"));
}
虽然 StackTrace 在上面运行得非常快,并且在大多数情况下不会成为性能问题,但调用者信息仍然快得多。在 1000 次迭代的样本中,我将其计时速度提高了 40 倍。
CachingHelpers.WhoseThere("wrong name!");
==> "wrong name!"
因为 CallerMemberName
仅替换默认值。
this
参数传递给扩展方法。此外,Olivier 是正确的,您可以传递一个值并且不应用 [CallerMemberName]
;相反,它充当通常使用默认值的覆盖。事实上,如果我们查看 IL,我们可以看到生成的方法与通常为 [opt]
arg 发出的方法没有什么不同,因此 CallerMemberName
的注入是 CLR 行为。最后,文档:“调用者信息属性 [...] 影响参数被省略时传入的默认值”
async
友好,StackFrame
不会帮助您。也不影响从 lambda 调用。
快速回顾这两种方法,速度比较是重要部分。
在编译时确定调用者
static void Log(object message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string fileName = "",
[CallerLineNumber] int lineNumber = 0)
{
// we'll just use a simple Console write for now
Console.WriteLine("{0}({1}):{2} - {3}", fileName, lineNumber, memberName, message);
}
使用堆栈确定调用者
static void Log(object message)
{
// frame 1, true for source info
StackFrame frame = new StackFrame(1, true);
var method = frame.GetMethod();
var fileName = frame.GetFileName();
var lineNumber = frame.GetFileLineNumber();
// we'll just use a simple Console write for now
Console.WriteLine("{0}({1}):{2} - {3}", fileName, lineNumber, method.Name, message);
}
两种方法的比较
Time for 1,000,000 iterations with Attributes: 196 ms
Time for 1,000,000 iterations with StackTrace: 5096 ms
所以你看,使用属性要快得多!事实上,速度快了近 25 倍。
我们可以通过仅实例化我们实际需要的帧而不是整个堆栈来稍微改进阿萨德先生的代码(当前接受的答案):
new StackFrame(1).GetMethod().Name;
这可能会表现得更好一些,尽管很可能它仍然必须使用完整的堆栈来创建单个帧。此外,它仍然有 Alex Lyman 指出的相同警告(优化器/本机代码可能会破坏结果)。最后,您可能需要检查以确保 new StackFrame(1)
或 .GetFrame(1)
不会返回 null
,尽管这种可能性看起来不太可能。
请参阅此相关问题:Can you use reflection to find the name of the currently executing method?
new ClassName(…)
是否可能等于 null?
一般来说,您可以使用 System.Diagnostics.StackTrace
类获取 System.Diagnostics.StackFrame
,然后使用 GetMethod()
方法获取 System.Reflection.MethodBase
对象。但是,这种方法有 some caveats:
它代表运行时堆栈——优化可以内联一个方法,您不会在堆栈跟踪中看到该方法。它不会显示任何本机框架,因此如果您的方法甚至有可能被本机方法调用,这将不起作用,实际上目前没有可用的方法来做到这一点。
(注意:我只是在扩展 Firas Assad 提供的 the answer。)
从 .NET 4.5 开始,您可以使用 Caller Information 属性:
CallerFilePath - 调用函数的源文件;
CallerLineNumber - 调用函数的代码行;
CallerMemberName - 调用函数的成员。 public void WriteLine( [CallerFilePath] string callerFilePath = "", [CallerLineNumber] long callerLineNumber = 0, [CallerMemberName] string callerMember = "") { Debug.WriteLine("呼叫者文件路径:{0},呼叫者行号:{1 }, 调用者成员:{2}", callerFilePath, callerLineNumber, callerMember); }
此功能也存在于“.NET Core”和“.NET Standard”中。
参考
Microsoft - 呼叫者信息 (C#) Microsoft - CallerFilePathAttribute 类 Microsoft - CallerLineNumberAttribute 类 Microsoft - CallerMemberNameAttribute 类
显然这是一个迟到的答案,但如果您可以使用 .NET 4.5 或更高版本,我有更好的选择:
internal static void WriteInformation<T>(string text, [CallerMemberName]string method = "")
{
Console.WriteLine(DateTime.Now.ToString() + " => " + typeof(T).FullName + "." + method + ": " + text);
}
这将打印当前日期和时间,然后是“Namespace.ClassName.MethodName”并以“:text”结尾。样本输出:
6/17/2016 12:41:49 PM => WpfApplication.MainWindow..ctor: MainWindow initialized
样品用途:
Logger.WriteInformation<MainWindow>("MainWindow initialized");
请注意,由于优化,这样做在发布代码中是不可靠的。此外,在沙盒模式(网络共享)下运行应用程序根本不允许您抓取堆栈帧。
考虑 aspect-oriented programming (AOP),例如 PostSharp,它不是从您的代码中调用,而是修改您的代码,因此始终知道它在哪里。
/// <summary>
/// Returns the call that occurred just before the "GetCallingMethod".
/// </summary>
public static string GetCallingMethod()
{
return GetCallingMethod("GetCallingMethod");
}
/// <summary>
/// Returns the call that occurred just before the the method specified.
/// </summary>
/// <param name="MethodAfter">The named method to see what happened just before it was called. (case sensitive)</param>
/// <returns>The method name.</returns>
public static string GetCallingMethod(string MethodAfter)
{
string str = "";
try
{
StackTrace st = new StackTrace();
StackFrame[] frames = st.GetFrames();
for (int i = 0; i < st.FrameCount - 1; i++)
{
if (frames[i].GetMethod().Name.Equals(MethodAfter))
{
if (!frames[i + 1].GetMethod().Name.Equals(MethodAfter)) // ignores overloaded methods.
{
str = frames[i + 1].GetMethod().ReflectedType.FullName + "." + frames[i + 1].GetMethod().Name;
break;
}
}
}
}
catch (Exception) { ; }
return str;
}
也许你正在寻找这样的东西:
StackFrame frame = new StackFrame(1);
frame.GetMethod().Name; //Gets the current method name
MethodBase method = frame.GetMethod();
method.DeclaringType.Name //Gets the current class name
private static MethodBase GetCallingMethod()
{
return new StackFrame(2, false).GetMethod();
}
private static Type GetCallingType()
{
return new StackFrame(2, false).GetMethod().DeclaringType;
}
精彩的课程在这里:http://www.csharp411.com/c-get-calling-method/
我使用的另一种方法是向相关方法添加参数。例如,使用 void Foo(string context)
代替 void Foo()
。然后传入一些指示调用上下文的唯一字符串。
如果您只需要调用者/上下文进行开发,则可以在发货前删除 param
。
要获取方法名称和类名称,请尝试以下操作:
public static void Call()
{
StackTrace stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(1).GetMethod();
var className = methodName.DeclaringType.Name.ToString();
Console.WriteLine(methodName.Name + "*****" + className );
}
Firas Assaad 回答的额外信息。
我在 .net core 2.1 中使用了 new StackFrame(1).GetMethod().Name;
和依赖注入,并且我将调用方法作为“开始”。
我尝试使用 [System.Runtime.CompilerServices.CallerMemberName] string callerName = ""
,它给了我正确的调用方法
我们也可以使用 lambda 来查找调用者。
假设您有一个由您定义的方法:
public void MethodA()
{
/*
* Method code here
*/
}
你想找到它的来电者。
1. 更改方法签名,使我们有一个 Action 类型的参数(Func 也可以):
public void MethodA(Action helperAction)
{
/*
* Method code here
*/
}
2. Lambda 名称不是随机生成的。规则似乎是: >
private MethodInfo GetCallingMethodInfo(string funcName)
{
return GetType().GetMethod(
funcName.Substring(1,
funcName.IndexOf(">", 1, StringComparison.Ordinal) - 1)
);
}
3. 当我们调用 MethodA 时,Action/Func 参数必须由调用者方法生成。例子:
MethodA(() => {});
4. 在 MethodA 中,我们现在可以调用上面定义的辅助函数并找到调用者方法的 MethodInfo。
例子:
MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;
就够了,我想。