Dotnet Patterns

affaan-m/ECC/skills/dotnet-patterns

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

Idiomatic C# and .NET patterns, conventions, dependency injection, async/await, and best practices for building robust, maintainable .NET applications. Use when writing or reviewing C# / .NET code — DI, async, or general conventions.

AI 產生的概覽

關於道地 C# 與 .NET 模式的參考指南,涵蓋相依性注入、async/await 與編碼慣例。

功能
提供道地的 C# 與 .NET 編碼模式和慣例,用於建構易於維護的應用程式。內容涵蓋不可變性、明確可空性與存取修飾詞、相依性注入、async/await 用法、選項模式與結果模式、以 EF Core 為基礎的儲存庫模式、中介軟體、最小 API 以及防護子句。也列出反模式及其修正建議。此技能僅包含說明,不會產生檔案或指令碼。
適用情境
適用於撰寫、審查或重構 C# 與 .NET 程式碼時。也適合設計 ASP.NET Core 服務架構,或決定相依性注入、非同步及一般慣例時使用。
執行需求
不需要任何工具、套件或認證;僅為說明文件,不含指令碼或資源。

.NET Development Patterns

Idiomatic C# and .NET patterns for building robust, performant, and maintainable applications.

When to Activate

  • Writing new C# code
  • Reviewing C# code
  • Refactoring existing .NET applications
  • Designing service architectures with ASP.NET Core

Core Principles

1. Prefer Immutability

Use records and init-only properties for data models. Mutability should be an explicit, justified choice.

csharp
// Good: Immutable value objectpublic sealed record Money(decimal Amount, string Currency);
// Good: Immutable DTO with init setterspublic sealed class CreateOrderRequest{    public required string CustomerId { get; init; }    public required IReadOnlyList<OrderItem> Items { get; init; }}
// Bad: Mutable model with public setterspublic class Order{    public string CustomerId { get; set; }    public List<OrderItem> Items { get; set; }}

2. Explicit Over Implicit

Be clear about nullability, access modifiers, and intent.

csharp
// Good: Explicit access modifiers and nullabilitypublic sealed class UserService{    private readonly IUserRepository _repository;    private readonly ILogger<UserService> _logger;
    public UserService(IUserRepository repository, ILogger<UserService> logger)    {        _repository = repository ?? throw new ArgumentNullException(nameof(repository));        _logger = logger ?? throw new ArgumentNullException(nameof(logger));    }
    public async Task<User?> FindByIdAsync(Guid id, CancellationToken cancellationToken)    {        return await _repository.FindByIdAsync(id, cancellationToken);    }}

3. Depend on Abstractions

Use interfaces for service boundaries. Register via DI container.

csharp
// Good: Interface-based dependencypublic interface IOrderRepository{    Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken);    Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken);    Task AddAsync(Order order, CancellationToken cancellationToken);}
// Registrationbuilder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();

Async/Await Patterns

Proper Async Usage

csharp
// Good: Async all the way, with CancellationTokenpublic async Task<OrderSummary> 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 asyncpublic OrderSummary GetOrderSummary(Guid orderId){    var order = _repository.FindByIdAsync(orderId, CancellationToken.None).Result; // Deadlock risk    return new OrderSummary(order);}

Parallel Async Operations

csharp
// Good: Concurrent independent operationspublic async Task<DashboardData> 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);}

Options Pattern

Bind configuration sections to strongly-typed objects.

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;}
// Registrationbuilder.Services.Configure<SmtpOptions>(    builder.Configuration.GetSection(SmtpOptions.SectionName));
// Usage via injectionpublic class EmailService(IOptions<SmtpOptions> options){    private readonly SmtpOptions _smtp = options.Value;}

Result Pattern

Return explicit success/failure instead of throwing for expected failures.

csharp
public sealed record Result<T>{    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<T> Success(T value) => new(value);    public static Result<T> Failure(string error) => new(error);}
// Usagepublic async Task<Result<Order>> PlaceOrderAsync(CreateOrderRequest request){    if (request.Items.Count == 0)        return Result<Order>.Failure("Order must contain at least one item");
    var order = Order.Create(request);    await _repository.AddAsync(order, CancellationToken.None);    return Result<Order>.Success(order);}

Repository Pattern with EF Core

csharp
public sealed class SqlOrderRepository : IOrderRepository{    private readonly AppDbContext _db;
    public SqlOrderRepository(AppDbContext db) => _db = db;
    public async Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken)    {        return await _db.Orders            .Include(o => o.Items)            .AsNoTracking()            .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);    }
    public async Task<IReadOnlyList<Order>> 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);    }}

Middleware and Pipeline

csharp
// Custom middlewarepublic sealed class RequestTimingMiddleware{    private readonly RequestDelegate _next;    private readonly ILogger<RequestTimingMiddleware> _logger;
    public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> 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);        }    }}

Minimal API Patterns

csharp
// Organized with route groupsvar 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);});

Guard Clauses

csharp
// Good: Early returns with clear validationpublic async Task<ProcessResult> 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);}

Anti-Patterns to Avoid

Anti-PatternFix
async void methodsReturn Task (except event handlers)
.Result or .Wait()Use await
catch (Exception) { }Handle or rethrow with context
new Service() in constructorsUse constructor injection
public fieldsUse properties with appropriate accessors
dynamic in business logicUse generics or explicit types
Mutable static stateUse DI scoping or ConcurrentDictionary
string.Format in loopsUse StringBuilder or interpolated string handlers

來源與署名

來源:affaan-m/ECC位於skills/dotnet-patterns提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架