coder-csharp-error-handling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese<skill_overview>
<purpose>Implement robust error handling following modern .NET patterns</purpose>
<triggers>
<trigger>Handling exceptions in services</trigger>
<trigger>Implementing validation logic</trigger>
<trigger>Designing API error responses</trigger>
<trigger>Choosing Result pattern vs exceptions</trigger>
<trigger>Setting up global exception handling</trigger>
</triggers>
<sources>
<source url="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/">Microsoft Exception Handling</source>
</sources>
</skill_overview>
<exceptions_vs_result>
<use_exceptions_when>
<case>Truly exceptional, unexpected failures</case>
<case>System-level errors (I/O, network, hardware)</case>
<case>In constructors (can't return Result)</case>
<case>Framework boundaries expecting exceptions</case>
<case>Programming errors (null, out of range)</case>
</use_exceptions_when>
<use_result_when>
<case>Expected business failures (validation, not found)</case>
<case>Multiple possible failure modes</case>
<case>Performance-critical hot paths</case>
<case>Domain layer business logic</case>
<case>Compile-time safety for error handling</case>
</use_result_when>
<example_comparison>
<code>
// Exception: Unexpected failure
if (connection == null)
throw new InvalidOperationException("Connection not initialized");
// Result: Expected business outcome
public Result<User> GetUser(int id)
{
var user = _repository.Find(id);
if (user == null)
return Result.Fail("User not found"); // Expected case
return Result.Ok(user);
}
</code>
</example_comparison>
</exceptions_vs_result>
<exception_best_practices>
<throwing>
<rule>Throw for truly exceptional conditions only</rule>
<rule>Use specific exception types</rule>
<rule>Include context in exception message</rule>
<example>
<code>
// Good: Specific exception with context
throw new ArgumentNullException(nameof(email), "Email is required for user creation");
// Good: Use built-in guard methods (.NET 6+)
ArgumentNullException.ThrowIfNull(user);
ArgumentException.ThrowIfNullOrEmpty(email);
// Bad: Generic exception
throw new Exception("Error occurred");
</code>
</example>
</throwing>
<catching>
<rule>Catch specific exceptions first, general last</rule>
<rule>Use exception filters (when clause) for fine-grained control</rule>
<rule>Always use "throw;" not "throw ex;" to preserve stack trace</rule>
<example>
<code>
try
{
await ProcessOrderAsync(order);
}
catch (ValidationException ex)
{
_logger.LogWarning(ex, "Validation failed for order {OrderId}", order.Id);
return BadRequest(ex.Errors);
}
catch (NotFoundException ex)
{
return NotFound(ex.Message);
}
catch (Exception ex) when (ex is TimeoutException or HttpRequestException)
{
_logger.LogWarning(ex, "Transient error, will retry");
throw; // Preserve stack trace!
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error processing order {OrderId}", order.Id);
throw; // Never "throw ex;"
}
</code>
</example>
</catching>
<custom_exceptions>
<template>
<code>
public class OrderProcessingException : Exception
{
public string OrderId { get; }
public string ErrorCode { get; }
public OrderProcessingException(string orderId, string message, string errorCode)
: base(message)
{
OrderId = orderId;
ErrorCode = errorCode;
}
public OrderProcessingException(string orderId, string message, Exception inner)
: base(message, inner)
{
OrderId = orderId;
}}
</code>
</template>
</custom_exceptions>
</exception_best_practices>
<result_pattern>
<libraries>
<library name="FluentResults">Rich API, multiple errors, chaining</library>
<library name="ErrorOr">Clean fluent API, functional style</library>
<library name="OneOf">Discriminated unions, exhaustive matching</library>
</libraries>
<erroror_example>
<code>
public async Task<ErrorOr<Order>> CreateOrderAsync(CreateOrderCommand cmd)
{
if (cmd.Items.Count == 0)
return Error.Validation("Order must have items");
var customer = await _customerRepo.FindAsync(cmd.CustomerId);
if (customer == null)
return Error.NotFound("Customer not found");
if (!customer.IsActive)
return Error.Validation("Customer account is inactive");
var order = new Order(cmd);
await _orderRepo.AddAsync(order);
return order;}
// Controller usage
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderDto dto)
{
var result = await _orderService.CreateOrderAsync(dto.ToCommand());
return result.Match(
order => Ok(new OrderDto(order)),
errors => Problem(errors.First().Description)
);}
</code>
</erroror_example>
<chaining>
<code>
public async Task<ErrorOr<OrderResult>> ProcessOrderAsync(int orderId)
{
return await ValidateOrderAsync(orderId)
.Then(order => CalculatePriceAsync(order))
.Then(order => ProcessPaymentAsync(order))
.Then(order => SendConfirmationAsync(order));
}
</code>
</chaining>
</result_pattern>
<validation>
<fluentvalidation recommended="true">
<description>Powerful, fluent validation with separation of concerns</description>
<example>
<code>
public class CreateUserValidator : AbstractValidator<CreateUserDto>
{
public CreateUserValidator(IUserRepository repository)
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format")
.MustAsync(async (email, ct) =>
!await repository.ExistsAsync(email))
.WithMessage("Email already registered");
RuleFor(x => x.Password)
.NotEmpty()
.MinimumLength(8)
.Matches("[A-Z]").WithMessage("Must contain uppercase")
.Matches("[a-z]").WithMessage("Must contain lowercase")
.Matches("[0-9]").WithMessage("Must contain number");
RuleFor(x => x.Age)
.InclusiveBetween(18, 120);
}}
</code>
</example>
</fluentvalidation>
<data_annotations>
<description>Simple validation with attributes</description>
<use_for>DTOs, simple input validation</use_for>
<example>
<code>
public class CreateUserDto
{
[Required]
[EmailAddress]
public string Email { get; init; }
[Required]
[MinLength(8)]
public string Password { get; init; }
[Range(18, 120)]
public int Age { get; init; }}
</code>
</example>
</data_annotations>
<validation_layers>
<layer name="Controller">Input format validation (DataAnnotations)</layer>
<layer name="Service">Business rules (FluentValidation)</layer>
<layer name="Domain">Invariants (guard clauses, exceptions)</layer>
</validation_layers>
</validation>
<aspnetcore_error_handling>
<iexceptionhandler recommended="true">
<description>Global exception handling (.NET 8+)</description>
<example>
<code>
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken ct)
{
_logger.LogError(exception, "Unhandled exception: {Message}", exception.Message);
var problemDetails = exception switch
{
ValidationException ex => new ProblemDetails
{
Type = "https://example.com/validation-error",
Title = "Validation Error",
Status = 400,
Detail = ex.Message
},
NotFoundException => new ProblemDetails
{
Type = "https://example.com/not-found",
Title = "Not Found",
Status = 404
},
_ => new ProblemDetails
{
Type = "https://example.com/internal-error",
Title = "Internal Server Error",
Status = 500,
Detail = "An unexpected error occurred"
}
};
problemDetails.Extensions["traceId"] = httpContext.TraceIdentifier;
httpContext.Response.StatusCode = problemDetails.Status ?? 500;
await httpContext.Response.WriteAsJsonAsync(problemDetails, ct);
return true;
}}
// Registration
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
app.UseExceptionHandler();
</code>
</example>
</iexceptionhandler>
<problem_details>
<description>RFC 7807 standard error format</description>
<format>
<code>
{
"type": "https://example.com/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Email is required",
"instance": "/api/users",
"traceId": "00-abc123..."
}
</code>
</format>
</problem_details>
</aspnetcore_error_handling>
<logging_errors>
<rules>
<rule>Always include exception object: LogError(ex, "message")</rule>
<rule>Add context: orderId, userId, operation name</rule>
<rule>Use structured logging with templates</rule>
<rule>Include correlation ID for tracing</rule>
</rules>
<example>
<code>
try
{
await ProcessOrderAsync(order);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to process order {OrderId} for customer {CustomerId}. Amount: {Amount}",
order.Id, order.CustomerId, order.TotalAmount);
throw;
}
</code>
</example>
<never_log>
<item>Passwords (even hashed)</item>
<item>Credit card numbers</item>
<item>API keys and tokens</item>
<item>Personal identification numbers</item>
</never_log>
</logging_errors>
<anti_patterns>
<avoid name="empty_catch">
<bad>catch (Exception) { }</bad>
<why>Swallows errors silently</why>
</avoid>
<avoid name="catch_and_throw_new">
<bad>catch (Exception ex) { throw new Exception("Error", ex); }</bad>
<why>Loses original exception type</why>
</avoid>
<avoid name="throw_ex">
<bad>throw ex;</bad>
<why>Resets stack trace</why>
<good>throw;</good>
</avoid>
<avoid name="exception_for_flow_control">
<bad>Using exceptions for expected business logic</bad>
<why>Expensive, unclear intent</why>
<good>Use Result pattern for expected failures</good>
</avoid>
</anti_patterns>
<skill_overview>
<purpose>遵循现代.NET模式实现健壮的错误处理</purpose>
<triggers>
<trigger>在服务中处理异常</trigger>
<trigger>实现验证逻辑</trigger>
<trigger>设计API错误响应</trigger>
<trigger>选择Result模式还是异常</trigger>
<trigger>设置全局异常处理</trigger>
</triggers>
<sources>
<source url="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/">Microsoft异常处理文档</source>
</sources>
</skill_overview>
<exceptions_vs_result>
<use_exceptions_when>
<case>真正异常、意外的故障</case>
<case>系统级错误(I/O、网络、硬件)</case>
<case>构造函数中(无法返回Result)</case>
<case>框架边界预期使用异常的场景</case>
<case>编程错误(空值、超出范围)</case>
</use_exceptions_when>
<use_result_when>
<case>预期的业务失败(验证失败、资源未找到)</case>
<case>存在多种可能的失败模式</case>
<case>性能关键的热路径</case>
<case>领域层业务逻辑</case>
<case>错误处理的编译时安全性</case>
</use_result_when>
<example_comparison>
<code>
// Exception: Unexpected failure
if (connection == null)
throw new InvalidOperationException("Connection not initialized");
// Result: Expected business outcome
public Result<User> GetUser(int id)
{
var user = _repository.Find(id);
if (user == null)
return Result.Fail("User not found"); // Expected case
return Result.Ok(user);
}
</code>
</example_comparison>
</exceptions_vs_result>
<exception_best_practices>
<throwing>
<rule>仅针对真正异常的情况抛出异常</rule>
<rule>使用特定的异常类型</rule>
<rule>在异常消息中包含上下文信息</rule>
<example>
<code>
// Good: Specific exception with context
throw new ArgumentNullException(nameof(email), "Email is required for user creation");
// Good: Use built-in guard methods (.NET 6+)
ArgumentNullException.ThrowIfNull(user);
ArgumentException.ThrowIfNullOrEmpty(email);
// Bad: Generic exception
throw new Exception("Error occurred");
</code>
<comments>
<comment>// 良好实践:带上下文的特定异常</comment>
<comment>// 良好实践:使用内置守卫方法(.NET 6+)</comment>
<comment>// 不良实践:通用异常</comment>
</comments>
</example>
</throwing>
<catching>
<rule>先捕获特定异常,最后捕获通用异常</rule>
<rule>使用异常过滤器(when子句)实现细粒度控制</rule>
<rule>始终使用"throw;"而非"throw ex;"以保留堆栈跟踪</rule>
<example>
<code>
try
{
await ProcessOrderAsync(order);
}
catch (ValidationException ex)
{
_logger.LogWarning(ex, "Validation failed for order {OrderId}", order.Id);
return BadRequest(ex.Errors);
}
catch (NotFoundException ex)
{
return NotFound(ex.Message);
}
catch (Exception ex) when (ex is TimeoutException or HttpRequestException)
{
_logger.LogWarning(ex, "Transient error, will retry");
throw; // Preserve stack trace!
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error processing order {OrderId}", order.Id);
throw; // Never "throw ex;"
}
</code>
</example>
</catching>
<custom_exceptions>
<template>
<code>
public class OrderProcessingException : Exception
{
public string OrderId { get; }
public string ErrorCode { get; }
public OrderProcessingException(string orderId, string message, string errorCode)
: base(message)
{
OrderId = orderId;
ErrorCode = errorCode;
}
public OrderProcessingException(string orderId, string message, Exception inner)
: base(message, inner)
{
OrderId = orderId;
}}
</code>
</template>
</custom_exceptions>
</exception_best_practices>
<result_pattern>
<libraries>
<library name="FluentResults">丰富的API、支持多错误、链式调用</library>
<library name="ErrorOr">简洁的流畅API、函数式风格</library>
<library name="OneOf">区分联合类型、穷尽匹配</library>
</libraries>
<erroror_example>
<code>
public async Task<ErrorOr<Order>> CreateOrderAsync(CreateOrderCommand cmd)
{
if (cmd.Items.Count == 0)
return Error.Validation("Order must have items");
var customer = await _customerRepo.FindAsync(cmd.CustomerId);
if (customer == null)
return Error.NotFound("Customer not found");
if (!customer.IsActive)
return Error.Validation("Customer account is inactive");
var order = new Order(cmd);
await _orderRepo.AddAsync(order);
return order;}
// Controller usage
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderDto dto)
{
var result = await _orderService.CreateOrderAsync(dto.ToCommand());
return result.Match(
order => Ok(new OrderDto(order)),
errors => Problem(errors.First().Description)
);}
</code>
</erroror_example>
<chaining>
<code>
public async Task<ErrorOr<OrderResult>> ProcessOrderAsync(int orderId)
{
return await ValidateOrderAsync(orderId)
.Then(order => CalculatePriceAsync(order))
.Then(order => ProcessPaymentAsync(order))
.Then(order => SendConfirmationAsync(order));
}
</code>
</chaining>
</result_pattern>
<validation>
<fluentvalidation recommended="true">
<description>功能强大的流畅式验证,关注点分离</description>
<example>
<code>
public class CreateUserValidator : AbstractValidator<CreateUserDto>
{
public CreateUserValidator(IUserRepository repository)
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format")
.MustAsync(async (email, ct) =>
!await repository.ExistsAsync(email))
.WithMessage("Email already registered");
RuleFor(x => x.Password)
.NotEmpty()
.MinimumLength(8)
.Matches("[A-Z]").WithMessage("Must contain uppercase")
.Matches("[a-z]").WithMessage("Must contain lowercase")
.Matches("[0-9]").WithMessage("Must contain number");
RuleFor(x => x.Age)
.InclusiveBetween(18, 120);
}}
</code>
</example>
</fluentvalidation>
<data_annotations>
<description>使用特性的简单验证</description>
<use_for>DTO、简单输入验证</use_for>
<example>
<code>
public class CreateUserDto
{
[Required]
[EmailAddress]
public string Email { get; init; }
[Required]
[MinLength(8)]
public string Password { get; init; }
[Range(18, 120)]
public int Age { get; init; }}
</code>
</example>
</data_annotations>
<validation_layers>
<layer name="Controller">输入格式验证(DataAnnotations)</layer>
<layer name="Service">业务规则(FluentValidation)</layer>
<layer name="Domain">不变量检查(守卫子句、异常)</layer>
</validation_layers>
</validation>
<aspnetcore_error_handling>
<iexceptionhandler recommended="true">
<description>全局异常处理(.NET 8+)</description>
<example>
<code>
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken ct)
{
_logger.LogError(exception, "Unhandled exception: {Message}", exception.Message);
var problemDetails = exception switch
{
ValidationException ex => new ProblemDetails
{
Type = "https://example.com/validation-error",
Title = "Validation Error",
Status = 400,
Detail = ex.Message
},
NotFoundException => new ProblemDetails
{
Type = "https://example.com/not-found",
Title = "Not Found",
Status = 404
},
_ => new ProblemDetails
{
Type = "https://example.com/internal-error",
Title = "Internal Server Error",
Status = 500,
Detail = "An unexpected error occurred"
}
};
problemDetails.Extensions["traceId"] = httpContext.TraceIdentifier;
httpContext.Response.StatusCode = problemDetails.Status ?? 500;
await httpContext.Response.WriteAsJsonAsync(problemDetails, ct);
return true;
}}
// Registration
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
app.UseExceptionHandler();
</code>
</example>
</iexceptionhandler>
<problem_details>
<description>RFC 7807标准错误格式</description>
<format>
<code>
{
"type": "https://example.com/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Email is required",
"instance": "/api/users",
"traceId": "00-abc123..."
}
</code>
</format>
</problem_details>
</aspnetcore_error_handling>
<logging_errors>
<rules>
<rule>始终包含异常对象:LogError(ex, "message")</rule>
<rule>添加上下文信息:orderId、userId、操作名称</rule>
<rule>使用带模板的结构化日志</rule>
<rule>包含用于追踪的关联ID</rule>
</rules>
<example>
<code>
try
{
await ProcessOrderAsync(order);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to process order {OrderId} for customer {CustomerId}. Amount: {Amount}",
order.Id, order.CustomerId, order.TotalAmount);
throw;
}
</code>
</example>
<never_log>
<item>密码(即使是哈希后的)</item>
<item>信用卡号</item>
<item>API密钥和令牌</item>
<item>个人识别号码</item>
</never_log>
</logging_errors>
<anti_patterns>
<avoid name="empty_catch">
<bad>catch (Exception) { }</bad>
<why>静默吞噬错误</why>
</avoid>
<avoid name="catch_and_throw_new">
<bad>catch (Exception ex) { throw new Exception("Error", ex); }</bad>
<why>丢失原始异常类型</why>
</avoid>
<avoid name="throw_ex">
<bad>throw ex;</bad>
<why>重置堆栈跟踪</why>
<good>throw;</good>
</avoid>
<avoid name="exception_for_flow_control">
<bad>Using exceptions for expected business logic</bad>
<why>性能开销大、意图不清晰</why>
<good>Use Result pattern for expected failures</good>
<comments>
<comment>// 不良实践:使用异常处理预期的业务逻辑</comment>
<comment>// 原因:性能开销大、意图不清晰</comment>
<comment>// 良好实践:对预期失败使用Result模式</comment>
</comments>
</avoid>
</anti_patterns>