Dotnet Patterns

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

C#と.NET言語固有のパターン、規約、依存性注入、async/await、およびロバストで保守可能な.NETアプリケーション構築のためのベストプラクティス。

AI 產生的概覽

提供道地的 C# 與 .NET 模式指引,用於撰寫、審查與重構易維護的 .NET 應用程式。

功能
此技能提供一份道地的 C# 與 .NET 模式參考,涵蓋不可變性、明確的可為 Null 性與存取修飾詞、相依性注入、async/await 用法、選項模式與結果模式、以 EF Core 為基礎的儲存庫、中介軟體、最小 API 以及防護子句。它也列出反模式與對應的修正建議。產出為文字指引與程式碼範例,而非可執行的成品。
適用情境
適用於撰寫新的 C# 程式碼、審查既有 C# 程式碼、重構 .NET 應用程式,或使用 ASP.NET Core 設計服務架構時。對象是希望一致套用慣例與最佳實務的開發人員。
執行需求
未包含指令碼或工具,僅為指示性指引。套用其中的範例需要 C#/.NET 開發環境,相關章節另涉及 ASP.NET Core 與 EF 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位於docs/ja-JP/skills/dotnet-patterns提交ef648e0

授權條款: 無授權條款

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

檢舉或申請下架