Dotnet Patterns

by affaan-mef648e01899bNo license275K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 3 days ago

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

Instructions onlySoftware Development
AI-generated overview

Guidance on idiomatic C# and .NET patterns for writing, reviewing and refactoring maintainable .NET applications.

What it does
This skill supplies a reference of idiomatic C# and .NET patterns, covering immutability, explicit nullability and access modifiers, dependency injection, async/await usage, the options and result patterns, repositories with EF Core, middleware, minimal APIs and guard clauses. It also lists anti-patterns with suggested fixes. It produces written guidance and code examples rather than executable artifacts.
When to use it
Use it when writing new C# code, reviewing existing C# code, refactoring .NET applications, or designing service architectures with ASP.NET Core. It is aimed at developers who want conventions and best practices applied consistently.
Requirements
No scripts or tooling are included; it is instructions only. Applying the examples assumes a C#/.NET development environment with ASP.NET Core and EF Core for the relevant sections.

.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

Source and attribution

Source:affaan-m/eccindocs/ja-JP/skills/dotnet-patternsat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal