ใน C# การ Re-throw Exception อย่างถูกต้องสำคัญมากสำหรับการ Debugging เพราะ Stack Trace บอกที่มาของ Error
วิธีที่ผิด (❌ Loses Stack Trace)
try
{
DoWork();
}
catch (Exception ex)
{
// ❌ สร้าง Exception ใหม่ - Stack Trace ชี้ที่นี่!
throw new Exception("Error occurred", ex);
// หรือ
throw ex; // ❌ Reset Stack Trace ให้ชี้ที่นี่ (Bad!)
}
วิธีที่ถูกต้อง (✅ Preserves Stack Trace)
1. ใช้ throw; เพียงอย่างเดียว (Best Practice)
static void Main()
{
try
{
Method2();
}
catch (Exception ex)
{
Console.WriteLine($"Caught in Main:\n{ex.StackTrace}");
}
}
static void Method2()
{
try
{
Method1();
}
catch
{
// ✅ Re-throw Original Exception - Stack Trace คงเดิม
throw;
}
}
static void Method1()
{
throw new InvalidOperationException("Original Error");
}
ผลลัพธ์ Stack Trace (ถูกต้อง)
Caught in Main:
at Program.Method1() in Program.cs:line 30
at Program.Method2() in Program.cs:line 22
at Program.Main() in Program.cs:line 10
กรณีต้อง Wrap Exception (Add Context)
หากต้องเพิ่ม Context ให้ใช้ ExceptionDispatchInfo (.NET 4.5+) หรือ Constructor ที่รับ innerException
1. ExceptionDispatchInfo.Capture (Best for Preserving Exact Stack Trace)
using System.Runtime.ExceptionServices;
try
{
RiskyOperation();
}
catch (Exception ex)
{
// Capture Current Stack Trace
ExceptionDispatchInfo edi = ExceptionDispatchInfo.Capture(ex);
// ทำ Logging/Context อื่นๆ
Logger.Log("Failed in RiskyOperation", ex);
// Re-throw พร้อม Stack Trace เดิมสมบูรณ์
edi.Throw(); // หรือ throw edi.SourceException; (แต่ Throw() ดีกว่า)
}
2. ใช้ Constructor (innerException) - Standard Way
try
{
ProcessOrder(orderId);
}
catch (Exception ex)
{
// ✅ เก็บ Original Stack Trace ใน InnerException
throw new OrderProcessingException($"Failed to process order {orderId}", ex);
}
// Custom Exception
public class OrderProcessingException : Exception
{
public OrderProcessingException(string message, Exception inner)
: base(message, inner) { }
}
3. Throw Helper Method (Pattern)
public static void ThrowWithContext(Exception ex, string context)
{
// Log context
Logger.Error($"{context}: {ex.Message}");
// Re-throw preserving stack trace
ExceptionDispatchInfo.Capture(ex).Throw();
}
// ใช้งาน
catch (Exception ex)
{
ThrowWithContext(ex, "UserService.GetUser");
}
Async Exception Handling
// Async Method - Exception ถูก Wrap ใน Task
public async Task GetUserAsync(int id)
{
try
{
return await _repo.GetAsync(id);
}
catch (Exception ex)
{
// ✅ throw; ใน async ก็ Preserve Stack Trace ได้ (Compiler Handle)
throw;
}
}
// Caller
try
{
var user = await GetUserAsync(1);
}
catch (Exception ex)
{
// Stack Trace จะชี้ไปที่ _repo.GetAsync และ GetUserAsync
}
สรุป Best Practices
| Scenario | Code | Stack Trace |
|---|---|---|
| Re-throw เดิม | throw; |
✅ คงเดิมสมบูรณ์ |
| Wrap + Context | throw new MyEx("msg", ex); |
✅ ใน InnerException |
| Re-throw หลายระดับ | throw; ทุกระดับ |
✅ คงเดิม |
| Log แล้ว Re-throw | Log(ex); throw; |
✅ คงเดิม |
| ❌ Throw ex | throw ex; |
❌ Reset ให้ชี้ Catch Block |
| ❌ New Exception | throw new Ex("msg"); |
❌ หายไป (No Inner) |
เครื่องมือช่วย
Ben.Demystifier(NuGet): Enhanced Stack Trace อ่านง่ายขึ้นSerilog/NLog: Log Exception.Full (รวม InnerExceptions)