`System.Diagnostics.Debug` 與 `Trace` 是 .NET 內建的診斷雙雄,提供**不修改正式程式碼即可插入偵錯資訊**的能力。兩者 API 幾乎相同,關鍵差異在於**編譯條件**:`Debug` 僅在 `DEBUG` 條件編譯符號定義時生效 (預設 Debug 建置);`Trace` 在 `TRACE` 定義時生效 (預設 Debug/Release 皆啟用)。本文完整解析核心方法、輸出管道、條件編譯、實戰技巧與最佳實務。
| 方法 | 用途 | 關鍵參數 |
|---|---|---|
| WriteLine(msg) | 輸出訊息並換行 | message, category(可選) |
| Write(msg) | 輸出訊息不換行 | message, category |
| WriteLineIf(condition, msg) | 條件成立時輸出 | condition, message, category |
| WriteIf(condition, msg) | 條件成立時輸出不換行 | condition, message, category |
| Assert(condition, msg) | 條件失敗時輸出並中斷 (Debug) / 僅輸出 (Trace) | condition, message, detailMessage |
| Indent() | 增加縮進層級 | 無 |
| Unindent() | 減少縮進層級 | 無 |
| Flush() | 強制將緩衝區刷新到所有 Listener | 無 |
| Fail(msg) | 無條件輸出失敗訊息 | message, detailMessage |
`Debug` 與 `Trace` 共用靜態 `Listeners` 集合 (`TraceListenerCollection`),預設包含 `DefaultTraceListener` (輸出到 VS「輸出」視窗)。可動態新增/移除:
using System.Diagnostics;
using System.IO;
// 1. 主控台輸出
Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
// 2. 檔案輸出 (自動建立/附加)
var fileListener = new TextWriterTraceListener(
File.CreateText("AppTrace.log")) { Name = "FileLog" };
Debug.Listeners.Add(fileListener);
// 3. 事件日誌輸出 (需管理員權限建立來源)
if (!EventLog.SourceExists("MyApp"))
EventLog.CreateEventSource("MyApp", "Application");
Debug.Listeners.Add(new EventLogTraceListener("MyApp"));
// 4. 自訂 Listener (即時 UI 顯示、送往伺服器、資料庫...)
public class SignalRTraceListener : TraceListener
{
public override void Write(string message) => SendToServer(message);
public override void WriteLine(string message) => SendToServer(message + "\n");
private void SendToServer(string msg) { /* SignalR/HttpClient 送出 */ }
}
Debug.Listeners.Add(new SignalRTraceListener());
// 5. 移除預設 Listener (避免重複輸出到 VS 輸出視窗)
Debug.Listeners.Remove("Default");
using System;
using System.Diagnostics;
using System.Xml;
class Program
{
static void Main()
{
string product = "Widget";
int qty = 100;
double cost = 1.03;
// === Debug 區塊 (僅 Debug 建置生效) ===
Debug.WriteLine("=== Debug: 產品資訊開始 ===");
Debug.Indent(); // 縮進
Debug.WriteLine($"產品名稱: {product}");
Debug.WriteLine($"庫存數量: {qty}");
Debug.WriteLine($"單價: {cost:C}");
// 輸出物件 (呼叫 ToString)
XmlDocument xml = new XmlDocument();
Debug.WriteLine(xml); // 輸出型別名稱
// 分類輸出 (方便過濾)
Debug.WriteLine($"產品名稱: {product}", "欄位");
Debug.WriteLine($"庫存數量: {qty}", "欄位");
Debug.WriteLine($"單價: {cost:C}", "欄位");
Debug.WriteLine($"總價: {qty * cost:C}", "計算");
// 條件輸出
Debug.WriteLineIf(qty > 50, "庫存充足", "庫存檢查");
Debug.WriteLineIf(qty < 50, "庫存不足!", "庫存檢查");
// 斷言 (條件失敗才輸出)
Debug.Assert(cost > 1.0, "成本應大於 1.0");
Debug.Assert(cost < 1.0, "成本不應小於 1.0"); // 這會觸發
// 加入檔案 Listener
Debug.Listeners.Add(new TextWriterTraceListener("DebugOutput.txt"));
Debug.WriteLine("此訊息也會寫入檔案");
Debug.Unindent(); // 取消縮進
Debug.WriteLine("=== Debug: 產品資訊結束 ===");
Debug.Flush(); // 確保寫入檔案
// === Trace 區塊 (Debug/Release 皆生效) ===
Trace.WriteLine("=== Trace: 產品資訊開始 ===");
Trace.Indent();
Trace.WriteLine($"產品: {product}", "欄位");
Trace.WriteLineIf(qty > 50, "庫存正常", "狀態");
Trace.Assert(cost > 0, "成本必須為正數");
Trace.Unindent();
Trace.WriteLine("=== Trace: 產品資訊結束 ===");
Trace.Flush();
Console.WriteLine("按 Enter 結束...");
Console.ReadLine();
}
}
| 建置組態 | DEBUG 符號 | TRACE 符號 | Debug 類別 | Trace 類別 |
|---|---|---|---|---|
| Debug | ✅ 定義 | ✅ 定義 | ✅ 生效 | ✅ 生效 |
| Release | ❌ 未定義 | ✅ 定義 (預設) | ❌ 程式碼移除 | ✅ 生效 |
| 場景 | 使用 | 理由 |
|---|---|---|
| 開發時期偵錯、變數檢查、流程追蹤 | Debug | Release 自動移除,零生產開銷 |
| 生產環境監控、稽核追蹤、效能計數 | Trace | Release 保留,可透過 config 控制 |
| 單元測試驗證內部狀態 | Debug | 測試專案通常 Debug 建置 |
| 程式庫開發 (不確定消費者建置類型) | Trace | 確保訊息可被消費者攔截 |
| Assert 邏輯驗證 (開發期捕捉 Bug) | Debug.Assert | Release 不中斷執行 |
| Assert 關鍵業務不變量 (生產期也要檢查) | Trace.Assert | 生產環境也記錄異常狀態 |
`Debug` 與 `Trace` 是 .NET 開發者最貼身的診斷工具。掌握關鍵差異——**Debug 給開發者看 (Release 消失),Trace 給營運者看 (Release 保留)**——並善用 `Listener` 架構將訊息導向檔案、事件日誌、雲端、即時儀表板,能大幅提升偵錯效率與生產環境可觀測性。現代 .NET 應用建議搭配 **Serilog / NLog / Microsoft.Extensions.Logging** 等結構化日誌框架,但 `Debug/Trace` 仍是零依賴、零設定、即刻可用的基礎利器。