Exception Hierarchy ใน .NET
System.Object
└─ System.Exception
├─ System.SystemException (Base สำหรับ System Errors)
│ ├─ ArgumentException
│ ├─ NullReferenceException
│ ├─ IndexOutOfRangeException
│ ├─ InvalidOperationException
│ ├─ OutOfMemoryException
│ └─ StackOverflowException
│
└─ System.ApplicationException (Base สำหรับ User-defined Errors)
└─ (Custom Exceptions ของคุณ)
ความแตกต่างหลัก
| ด้าน | SystemException (System-level) | ApplicationException (App-level) |
|---|---|---|
| Base Class | System.SystemException | System.ApplicationException |
| แหล่งที่มา | CLR / .NET Framework | Application Code (Developer) |
| ลักษณะ Error | Non-recoverable, Fatal, Infrastructure | Recoverable, Business Logic, Validation |
| ตัวอย่าง | OutOfMemory, StackOverflow, NullReference, DB Connection Failed | InvalidInput, InsufficientFunds, BusinessRuleViolation |
| Handling | มักไม่ Catch (Let Crash/Log) | Catch และ Handle ด้วย Try-Catch |
| Customization | ไม่ควร Inherit | สร้าง Custom Exception ได้ |
| Purpose | แจ้ง Bug/Infrastructure Failure | แจ้ง Business Logic Error |
SystemException - ตัวอย่าง
// CLR Throws These Automatically
void SystemExamples()
{
// NullReferenceException - CLR Detect
string s = null;
Console.WriteLine(s.Length); // Throw NullReferenceException
// IndexOutOfRangeException
int[] arr = new int[3];
arr[5] = 10; // Throw IndexOutOfRangeException
// OutOfMemoryException - CLR Detect (Rare)
// var big = new byte[int.MaxValue];
// InvalidOperationException - Framework Throws
var list = new List();
var enumerator = list.GetEnumerator();
list.Add(1); // Modified during enumeration
enumerator.MoveNext(); // Throw InvalidOperationException
}
ApplicationException - Custom Exceptions
⚠️ Microsoft แนะนำ: อย่า Inherit จาก ApplicationException
ใช้
ใช้
Exception ตรงๆ หรือ Base Class ใดๆ ที่เหมาะสม ApplicationException ไม่ได้เพิ่มคุณค่า และถูก Deprecate ใน Spirit
Best Practice: Custom Exception Pattern
// Base Custom Exception
public abstract class AppException : Exception
{
public string ErrorCode { get; }
protected AppException(string errorCode, string message) : base(message)
{
ErrorCode = errorCode;
}
protected AppException(string errorCode, string message, Exception inner)
: base(message, inner) { ErrorCode = errorCode; }
}
// Specific Business Exceptions
public class ValidationException : AppException
{
public ValidationException(string message) : base("VALIDATION_ERROR", message) { }
public ValidationException(string field, string message)
: base("VALIDATION_ERROR", $"{field}: {message}") { }
}
public class BusinessRuleException : AppException
{
public BusinessRuleException(string rule, string message)
: base("BUSINESS_RULE_VIOLATION", $"Rule '{rule}': {message}") { }
}
public class NotFoundException : AppException
{
public NotFoundException(string entity, object id)
: base("NOT_FOUND", $"{entity} with id '{id}' not found") { }
}
// Usage
public void Transfer(Account from, Account to, decimal amount)
{
if (amount <= 0)
throw new ValidationException("Amount", "Must be positive");
if (from.Balance < amount)
throw new BusinessRuleException("SUFFICIENT_FUNDS", "Insufficient balance");
if (!Accounts.Contains(to.Id))
throw new NotFoundException("Account", to.Id);
// ...
}
Exception Handling Strategy
try
{
Transfer(from, to, amount);
}
catch (ValidationException ex)
{
// 400 Bad Request - Client Error
Log.Warning(ex, "Validation failed: {ErrorCode}", ex.ErrorCode);
return BadRequest(new { error = ex.ErrorCode, message = ex.Message });
}
catch (BusinessRuleException ex)
{
// 409 Conflict / 422 Unprocessable
Log.Warning(ex, "Business rule violated: {ErrorCode}", ex.ErrorCode);
return Conflict(new { error = ex.ErrorCode, message = ex.Message });
}
catch (NotFoundException ex)
{
// 404 Not Found
Log.Information(ex, "Resource not found: {ErrorCode}", ex.ErrorCode);
return NotFound(new { error = ex.ErrorCode, message = ex.Message });
}
catch (Exception ex) // SystemException, Unexpected
{
// 500 Internal Server Error
Log.Error(ex, "Unexpected error");
return StatusCode(500, new { error = "INTERNAL_ERROR", message = "An error occurred" });
}
Anti-Patterns
- ❌
catch (Exception)แล้ว Swallow (Log แค่บางครั้ง) - ❌ Throw
new Exception("message")(Generic, ไม่มี Type) - ❌ ใช้ Exception ควบคุม Flow (Control Flow)
- ❌ Catch แล้ว Throw ใหม่โดยไม่ Preserve Stack Trace (
throw exแทนthrow)
สรุป
- SystemException: CLR/Framework Throw, Fatal, ไม่ควร Catch เพื่อ Recover
- Custom Exception (จาก Exception): Business Logic, Recoverable, Catch & Handle ได้
- ApplicationException: Legacy Base Class, ไม่แนะนำให้ใช้