--- name: dotnet-patterns description: 惯用的C#和.NET模式、约定、依赖注入、async/await以及构建健壮、可维护的.NET应用程序的最佳实践。 origin: ECC --- # .NET 开发模式 用于构建健壮、高性能且可维护应用程序的惯用 C# 和 .NET 模式。 ## 何时激活 * 编写新的 C# 代码时 * 审查 C# 代码时 * 重构现有 .NET 应用程序时 * 使用 ASP.NET Core 设计服务架构时 ## 核心原则 ### 1. 优先使用不可变性 对数据模型使用记录和仅初始化属性。可变性应作为明确且有理由的选择。 ```csharp // Good: Immutable value object public sealed record Money(decimal Amount, string Currency); // Good: Immutable DTO with init setters public sealed class CreateOrderRequest { public required string CustomerId { get; init; } public required IReadOnlyList Items { get; init; } } // Bad: Mutable model with public setters public class Order { public string CustomerId { get; set; } public List Items { get; set; } } ``` ### 2. 显式优于隐式 明确表达可空性、访问修饰符和意图。 ```csharp // Good: Explicit access modifiers and nullability public sealed class UserService { private readonly IUserRepository _repository; private readonly ILogger _logger; public UserService(IUserRepository repository, ILogger logger) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task FindByIdAsync(Guid id, CancellationToken cancellationToken) { return await _repository.FindByIdAsync(id, cancellationToken); } } ``` ### 3. 依赖抽象 对服务边界使用接口。通过依赖注入容器注册。 ```csharp // Good: Interface-based dependency public interface IOrderRepository { Task FindByIdAsync(Guid id, CancellationToken cancellationToken); Task> FindByCustomerAsync(string customerId, CancellationToken cancellationToken); Task AddAsync(Order order, CancellationToken cancellationToken); } // Registration builder.Services.AddScoped(); ``` ## 异步/等待模式 ### 正确使用异步 ```csharp // Good: Async all the way, with CancellationToken public async Task GetOrderSummaryAsync( Guid orderId, CancellationToken cancellationToken) { var order = await _repository.FindByIdAsync(orderId, cancellationToken) ?? throw new NotFoundException($"Order {orderId} not found"); var customer = await _customerService.GetAsync(order.CustomerId, cancellationToken); return new OrderSummary(order, customer); } // Bad: Blocking on async public OrderSummary GetOrderSummary(Guid orderId) { var order = _repository.FindByIdAsync(orderId, CancellationToken.None).Result; // Deadlock risk return new OrderSummary(order); } ``` ### 并行异步操作 ```csharp // Good: Concurrent independent operations public async Task LoadDashboardAsync(CancellationToken cancellationToken) { var ordersTask = _orderService.GetRecentAsync(cancellationToken); var metricsTask = _metricsService.GetCurrentAsync(cancellationToken); var alertsTask = _alertService.GetActiveAsync(cancellationToken); await Task.WhenAll(ordersTask, metricsTask, alertsTask); return new DashboardData( Orders: await ordersTask, Metrics: await metricsTask, Alerts: await alertsTask); } ``` ## 选项模式 将配置节绑定到强类型对象。 ```csharp public sealed class SmtpOptions { public const string SectionName = "Smtp"; public required string Host { get; init; } public required int Port { get; init; } public required string Username { get; init; } public bool UseSsl { get; init; } = true; } // Registration builder.Services.Configure( builder.Configuration.GetSection(SmtpOptions.SectionName)); // Usage via injection public class EmailService(IOptions options) { private readonly SmtpOptions _smtp = options.Value; } ``` ## 结果模式 对预期失败返回显式成功/失败,而非抛出异常。 ```csharp public sealed record Result { public bool IsSuccess { get; } public T? Value { get; } public string? Error { get; } private Result(T value) { IsSuccess = true; Value = value; } private Result(string error) { IsSuccess = false; Error = error; } public static Result Success(T value) => new(value); public static Result Failure(string error) => new(error); } // Usage public async Task> PlaceOrderAsync(CreateOrderRequest request) { if (request.Items.Count == 0) return Result.Failure("Order must contain at least one item"); var order = Order.Create(request); await _repository.AddAsync(order, CancellationToken.None); return Result.Success(order); } ``` ## 使用 EF Core 的仓储模式 ```csharp public sealed class SqlOrderRepository : IOrderRepository { private readonly AppDbContext _db; public SqlOrderRepository(AppDbContext db) => _db = db; public async Task FindByIdAsync(Guid id, CancellationToken cancellationToken) { return await _db.Orders .Include(o => o.Items) .AsNoTracking() .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } public async Task> FindByCustomerAsync( string customerId, CancellationToken cancellationToken) { return await _db.Orders .Where(o => o.CustomerId == customerId) .OrderByDescending(o => o.CreatedAt) .AsNoTracking() .ToListAsync(cancellationToken); } public async Task AddAsync(Order order, CancellationToken cancellationToken) { _db.Orders.Add(order); await _db.SaveChangesAsync(cancellationToken); } } ``` ## 中间件与管道 ```csharp // Custom middleware public sealed class RequestTimingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public RequestTimingMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var stopwatch = Stopwatch.StartNew(); try { await _next(context); } finally { stopwatch.Stop(); _logger.LogInformation( "Request {Method} {Path} completed in {ElapsedMs}ms with status {StatusCode}", context.Request.Method, context.Request.Path, stopwatch.ElapsedMilliseconds, context.Response.StatusCode); } } } ``` ## 最小 API 模式 ```csharp // Organized with route groups var orders = app.MapGroup("/api/orders") .RequireAuthorization() .WithTags("Orders"); orders.MapGet("/{id:guid}", async ( Guid id, IOrderRepository repository, CancellationToken cancellationToken) => { var order = await repository.FindByIdAsync(id, cancellationToken); return order is not null ? TypedResults.Ok(order) : TypedResults.NotFound(); }); orders.MapPost("/", async ( CreateOrderRequest request, IOrderService service, CancellationToken cancellationToken) => { var result = await service.PlaceOrderAsync(request, cancellationToken); return result.IsSuccess ? TypedResults.Created($"/api/orders/{result.Value!.Id}", result.Value) : TypedResults.BadRequest(result.Error); }); ``` ## 守卫子句 ```csharp // Good: Early returns with clear validation public async Task ProcessPaymentAsync( PaymentRequest request, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(request); if (request.Amount <= 0) throw new ArgumentOutOfRangeException(nameof(request.Amount), "Amount must be positive"); if (string.IsNullOrWhiteSpace(request.Currency)) throw new ArgumentException("Currency is required", nameof(request.Currency)); // Happy path continues here without nesting var gateway = _gatewayFactory.Create(request.Currency); return await gateway.ChargeAsync(request, cancellationToken); } ``` ## 应避免的反模式 | 反模式 | 修复方案 | |---|---| | `async void` 方法 | 返回 `Task`(事件处理程序除外) | | `.Result` 或 `.Wait()` | 使用 `await` | | `catch (Exception) { }` | 处理或带上下文重新抛出 | | 构造函数中的 `new Service()` | 使用构造函数注入 | | `public` 字段 | 使用带适当访问器的属性 | | 业务逻辑中的 `dynamic` | 使用泛型或显式类型 | | 可变的 `static` 状态 | 使用依赖注入作用域或 `ConcurrentDictionary` | | 循环中的 `string.Format` | 使用 `StringBuilder` 或内插字符串处理程序 |