using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; namespace PCL.Core.Utils.Diagnostics; public static class StackHelper { private const string Unknown = ""; // 返回“直接调用者”的可读名称(例如 Namespace.Type.Method(paramTypes)) // includeNamespace: 是否包含命名空间 // includeParameters: 是否包含参数类型列表 // skipAppFrames: 额外跳过的应用层帧数量(例如你自己的日志包装器) [MethodImpl(MethodImplOptions.NoInlining)] public static string GetDirectCallerName( bool includeNamespace = true, bool includeParameters = false, int skipAppFrames = 0) { var st = new StackTrace(skipFrames: 1, fNeedFileInfo: false); var frame = st.GetFrame(skipAppFrames); var method = frame?.GetMethod(); if (method is null) return Unknown; method = _TryMapAsyncOrIterator(method); return _FormatMethod(method, includeNamespace, includeParameters); } // 获取前 maxFrames 层调用栈,返回格式化后的每一帧字符串 // needFileInfo=true 时会解析 PDB 以拿到文件名与行号(昂贵) [MethodImpl(MethodImplOptions.NoInlining)] public static IReadOnlyList GetStack( int maxFrames = 10, bool includeNamespace = true, bool includeParameters = false, bool needFileInfo = false) { if (maxFrames <= 0) return []; var st = new StackTrace(skipFrames: 1, fNeedFileInfo: needFileInfo); var list = new List(capacity: Math.Min(maxFrames, st.FrameCount)); for (int i = 0, added = 0; i < st.FrameCount && added < maxFrames; i++) { var method = st.GetFrame(i)?.GetMethod(); if (method is null) continue; method = _TryMapAsyncOrIterator(method); var sig = _FormatMethod(method, includeNamespace, includeParameters); if (needFileInfo) { var f = st.GetFrame(i); var file = f?.GetFileName(); var line = f?.GetFileLineNumber() ?? 0; if (!string.IsNullOrEmpty(file) && line > 0) { sig = $"{sig} ({System.IO.Path.GetFileName(file)}:{line})"; } } list.Add(sig); added++; } return list; } // 将 async/iterator 的 MoveNext 映射回原始方法名(尽力而为的启发式) private static MethodBase _TryMapAsyncOrIterator(MethodBase method) { if (method.Name != "MoveNext") return method; var dt = method.DeclaringType; if (dt is null) return method; // 典型生成类型名:d__12 或 g__Local|12_0 var name = dt.Name; var lt = name.IndexOf('<'); var gt = name.IndexOf('>'); if (lt >= 0 && gt > lt + 1) { var originalName = name.Substring(lt + 1, gt - lt - 1); // 在声明类型的外层类型里查找同名方法(可能存在重载,选第一个匹配) var parent = dt.DeclaringType ?? dt; // 迭代器常为嵌套到原类型里 foreach (var m in parent.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { if (string.Equals(m.Name, originalName, StringComparison.Ordinal)) return m; } } return method; } private static string _FormatMethod(MethodBase method, bool includeNamespace, bool includeParameters, bool includeParameterNamespace = false) { var sb = new StringBuilder(); var type = method.DeclaringType; if (type is not null) { sb.Append(_FormatTypeName(type, includeNamespace)); sb.Append('.'); } sb.Append(method.Name); if (method is MethodInfo { IsGenericMethod: true } mi) { sb.Append('['); var args = mi.GetGenericArguments(); for (var i = 0; i < args.Length; i++) { if (i > 0) sb.Append(", "); sb.Append(_FormatTypeName(args[i], false)); } sb.Append(']'); } if (includeParameters) { var ps = method.GetParameters(); sb.Append('('); for (var i = 0; i < ps.Length; i++) { if (i > 0) sb.Append(", "); sb.Append(_FormatTypeName(ps[i].ParameterType, includeParameterNamespace)); if (!string.IsNullOrEmpty(ps[i].Name)) { sb.Append(' ').Append(ps[i].Name); } } sb.Append(')'); } return sb.ToString(); } private static string _FormatTypeName(Type t, bool includeNamespace) { if (t.IsGenericType) { var def = t.GetGenericTypeDefinition(); var name = def.Name; var tick = name.IndexOf('`'); if (tick >= 0) name = name[..tick]; var ns = includeNamespace ? (def.Namespace is null ? "" : def.Namespace + ".") : ""; var sb = new StringBuilder(ns).Append(name).Append('['); var args = t.GetGenericArguments(); for (var i = 0; i < args.Length; i++) { if (i > 0) sb.Append(", "); sb.Append(_FormatTypeName(args[i], false)); } sb.Append(']'); return sb.ToString(); } return includeNamespace && t.Namespace is not null ? t.Namespace + "." + t.Name : t.Name; } }