最轻量可靠的方式是MethodBase.GetCurrentMethod().Name;异步方法必须用[CallerMemberName];StackTrace不可靠,仅限调试。

怎么在运行时拿到当前方法名
直接用 MethodBase.GetCurrentMethod().Name,这是最轻量、最可靠的方式。它不依赖异常、不触发栈遍历,也不受优化影响(Release 模式下依然有效)。
常见错误是误用 StackTrace 去扒第一帧——看似能用,但一开编译器优化(尤其是 .NET 6+ 的 tiered JIT)就可能跳过当前帧,返回上层调用者的名字。
- 只在调试/日志场景用,别放进高频路径(比如循环体内),
GetCurrentMethod()虽快,但比纯变量访问还是有开销 - 返回的是方法名(如
"DoWork"),不含类名或泛型参数;要完整签名得拼DeclaringType?.FullName - 如果方法是
async,拿到的是状态机生成的"MoveNext",不是原始方法名——这时必须用[CallerMemberName]
异步方法里怎么安全获取原始方法名
[CallerMemberName] 是唯一靠谱解法。编译器在编译期就把调用点的方法名塞进参数,默认值里,零运行时成本,且完全绕过 async 状态机干扰。
典型误用是试图在 async void 或 lambda 里靠反射抓名字——lambda 没“方法名”,async void 的堆栈又不可靠,结果要么空字符串,要么错乱。
- 必须作为可选参数写在方法签名末尾:
void Log(string msg, [CallerMemberName] string member = "") - 不能用于字段初始化、属性 getter/setter 的默认参数位置(编译报错)
- 在泛型方法里它返回的是调用处的实参名,不是定义处的
T,这点和预期一致
为什么 StackTrace.ToString() 不该用来取方法名
因为它是为诊断服务的,不是为程序逻辑设计的。输出格式随 .NET 版本、调试符号、JIT 行为浮动,解析极易断裂。
比如 StackTrace 在 .NET Core 3.1 和 .NET 6 上对内联函数的显示差异极大;没 PDB 文件时连行号都为空;Release 模式下还可能被裁掉帧。
- 错误现象:
StackTrace().ToString()返回空行、重复帧、或根本找不到当前方法 - 真实使用场景仅限于:手动生成错误报告、开发期快速定位,绝不该出现在生产日志主链路
- 若真要用,至少用
new StackTrace(0, false)避免捕获异常对象,减少 GC 压力
日志框架里方法名是怎么来的
主流库(如 Serilog、NLog)底层其实混合用了两种策略:[CallerMemberName] 优先,Fallback 到 MethodBase.GetCurrentMethod(),绝不用 StackTrace 解析。
你看到的日志里那个 MyClass.DoWork,大概率是日志方法声明了 [CallerMemberName] 参数,再由调用方无感传入——所以自己封装日志工具时,照这个模式抄就行。
- Serilog 的
ForContext不自动带方法名,得手动传;NLog 的${callsite}才真正走栈分析(且默认关掉) - 自定义日志辅助方法时,别图省事写
Log($"{DateTime.Now} {MethodBase...}")——字符串拼接会提前执行,哪怕日志级别被过滤掉也白跑一遍反射 - 跨线程(比如 Task.Run 里)调用时,
[CallerMemberName]仍生效,因为它绑定的是调用点,不是执行点
方法名看着小,但 async、JIT、日志性能、跨平台兼容性全在里头咬着。漏掉 [CallerMemberName] 的 async 场景,或者在 Release 下信了 StackTrace,基本等于埋了个静默错位 bug。










