using System;
using System.Diagnostics;
using System.Reflection;
public static class CallerInfo
{
///
/// 取得呼叫端方法資訊 (跳過 1 層 = 直接呼叫者)
///
public static string GetCaller(int skipFrames = 1)
{
var st = new StackTrace(true); // true = 捕獲檔案/行號 (需 PDB)
var frame = st.GetFrame(skipFrames); // 0=GetCaller, 1=直接呼叫者, 2=再上一層...
if (frame == null) return "Unknown";
var method = frame.GetMethod();
var declaringType = method.DeclaringType;
return $"Module: {method.Module.Name}\n" +
$"Namespace: {declaringType?.Namespace}\n" +
$"Class: {declaringType?.Name}\n" +
$"Method: {method.Name}\n" +
$"File: {frame.GetFileName()}:{frame.GetFileLineNumber()}";
}
}
public class OrderService
{
public void CreateOrder(OrderDto dto)
{
// 業務邏輯...
LogHelper.Info("訂單建立完成", CallerInfo.GetCaller());
}
}
public static class LogHelper
{
public static void Info(string message, string caller = "")
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}\nCaller:\n{caller}\n---");
}
}
using System.Runtime.CompilerServices;
public static class LogHelper
{
public static void Info(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"[{DateTime.Now}] {message} | Caller: {memberName} @ {filePath}:{lineNumber}");
}
}
// 呼叫端完全不需傳參
public void CreateOrder() => LogHelper.Info("訂單建立");
// 輸出: Caller: CreateOrder @ D:\Proj\OrderService.cs:42
優勢:編譯器自動填入常數,**零執行期反射開銷**、支援內聯最佳化、**混淆後仍正確** (屬性值為字面常數)。
| 方法 | 相對耗時 (100 萬次) | 適用場景 |
|---|---|---|
| `StackTrace` + 反射 | ~ 1,200 ms | 舊版 .NET、需完整堆疊、動態跳過層數 |
| `CallerMemberName` 等屬性 | ~ 0 ms (編譯期) | **新專案首選**、高頻 Logging、效能敏感 |
| `MethodBase.GetCurrentMethod()` | ~ 300 ms | 僅需目前方法名,不需呼叫者 |
public static class Log
{
// 內部使用 Caller 屬性,外部介面乾淨
public static void Debug(string msg,
[CallerMemberName] string member = "",
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) => Write(LogLevel.Debug, msg, member, file, line);
public static void Info(string msg,
[CallerMemberName] string member = "",
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) => Write(LogLevel.Info, msg, member, file, line);
// 核心寫入 (可接入 Serilog/NLog/log4net)
private static void Write(LogLevel level, string msg, string member, string file, int line)
{
var entry = new { Time = DateTime.UtcNow, Level = level, Message = msg, Caller = member, File = file, Line = line };
// Serilog.Log.Write(level, "{@Entry}", entry); // 結構化日誌
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(entry));
}
}
取得呼叫端方法名稱,**現代 .NET (4.5+) 應優先使用 `CallerMemberName` 等編譯期屬性**,零效能成本、語法簡潔。僅在**舊版框架、需動態跳過層數、需完整堆疊字串**時才使用 `StackTrace` 反射。Logging 框架 (Serilog/NLog) 內建支援 `Caller` 屬性,建議直接接入成熟庫而非自行實作。