Install any skill in seconds. Free to start, no credit card required.
Get Started Free →C#と.NET言語固有のパターン、規約、依存性注入、async/await、およびロバストで保守可能な.NETアプリケーション構築のためのベストプラクティス。
.claude/skills/affaan-m-dotnet-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 117% | 0% |
| case-19 | ✓→✓ | = Same ✓ | 102% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 88% | 0% |
用于构建健壮、高性能且可维护应用程序的惯用 C# 和 .NET 模式。
对数据模型使用记录和仅初始化属性。可变性应作为明确且有理由的选择。
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<OrderItem> Items { get; init; } } // Bad: Mutable model with public setters public class Order { public string CustomerId { get; set; } public List<OrderItem> Items { get; set; } }
明确表达可空性、访问修饰符和意图。
csharp// Good: Explicit access modifiers and nullability public 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); } }
对服务边界使用接口。通过依赖注入容器注册。
csharp// Good: Interface-based dependency public interface IOrderRepository { Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken); Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken); Task AddAsync(Order order, CancellationToken cancellationToken); } // Registration builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
csharp// Good: Async all the way, with CancellationToken public 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 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<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); }
将配置节绑定到强类型对象。
csharppublic 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<SmtpOptions>( builder.Configuration.GetSection(SmtpOptions.SectionName)); // Usage via injection public class EmailService(IOptions<SmtpOptions> options) { private readonly SmtpOptions _smtp = options.Value; }
对预期失败返回显式成功/失败,而非抛出异常。
csharppublic 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); } // Usage public 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); }
csharppublic 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); } }
csharp// Custom middleware public 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); } } }
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<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); }
| 反模式 | 修复方案 | |---|---| | async void 方法 | 返回 Task(事件处理程序除外) | | .Result 或 .Wait() | 使用 await | | catch (Exception) { } | 处理或带上下文重新抛出 | | 构造函数中的 new Service() | 使用构造函数注入 | | public 字段 | 使用带适当访问器的属性 | | 业务逻辑中的 dynamic | 使用泛型或显式类型 | | 可变的 static 状态 | 使用依赖注入作用域或 ConcurrentDictionary | | 循环中的 string.Format | 使用 StringBuilder 或内插字符串处理程序 |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | pass→pass | 12,386 | 11,849 | -4% | 1 | 1 | 0% | 2,363 | 4,767 | +102% | 0 | 0 | — |
case-01 | fail→fail | 18,404 | 13,941 | -24% | 1 | 1 | 0% | 4,535 | 5,750 | +27% | 0 | 0 | — |
case-02 | fail→pass | 11,963 | 11,211 | -6% | 1 | 1 | 0% | 2,446 | 4,326 | +77% | 0 | 0 | — |
case-03 | pass→pass | 9,551 | 8,021 | -16% | 1 | 1 | 0% | 2,060 | 3,866 | +88% | 0 | 0 | — |
case-04 | pass→pass | 12,753 | 7,572 | -41% | 1 | 1 | 0% | 2,086 | 3,584 | +72% | 0 | 0 | — |
case-05 | fail→pass | 12,097 | 12,480 | +3% | 1 | 1 | 0% | 2,226 | 4,559 | +105% | 0 | 0 | — |
case-06 | pass→pass | 16,355 | 12,419 | -24% | 1 | 1 | 0% | 2,819 | 4,786 | +70% | 0 | 0 | — |
case-07 | pass→pass | 12,213 | 10,148 | -17% | 1 | 1 | 0% | 1,981 | 4,067 | +105% | 0 | 0 | — |
case-08 | pass→pass | 10,341 | 10,818 | +5% | 1 | 1 | 0% | 2,011 | 4,162 | +107% | 0 | 0 | — |
case-09 | pass→pass | 12,086 | 13,741 | +14% | 1 | 1 | 0% | 2,345 | 4,854 | +107% | 0 | 0 | — |
case-10 | pass→pass | 15,983 | 11,703 | -27% | 1 | 1 | 0% | 2,248 | 4,341 | +93% | 0 | 0 | — |
case-11 | fail→pass | 9,602 | 8,304 | -14% | 1 | 1 | 0% | 1,731 | 3,757 | +117% | 0 | 0 | — |
case-12 | pass→pass | 9,047 | 7,074 | -22% | 1 | 1 | 0% | 1,681 | 3,551 | +111% | 0 | 0 | — |
case-13 | fail→fail | 14,816 | 15,044 | +2% | 1 | 1 | 0% | 2,654 | 4,993 | +88% | 0 | 0 | — |
case-14 | pass→pass | 8,708 | 7,507 | -14% | 1 | 1 | 0% | 1,596 | 3,614 | +126% | 0 | 0 | — |
case-20 | pass→pass | 10,714 | 7,421 | -31% | 1 | 1 | 0% | 1,897 | 3,578 | +89% | 0 | 0 | — |
case-15 | pass→pass | 10,062 | 10,600 | +5% | 1 | 1 | 0% | 1,847 | 4,198 | +127% | 0 | 0 | — |
case-16 | pass→pass | 16,420 | 12,098 | -26% | 1 | 1 | 0% | 2,823 | 4,605 | +63% | 0 | 0 | — |
case-17 | pass→pass | 7,152 | 6,473 | -9% | 1 | 1 | 0% | 1,391 | 3,572 | +157% | 0 | 0 | — |
case-18 | pass→pass | 6,229 | 5,107 | -18% | 1 | 1 | 0% | 1,024 | 3,168 | +209% | 0 | 0 | — |
case-21 | pass→pass | 10,767 | 10,126 | -6% | 1 | 1 | 0% | 1,900 | 3,936 | +107% | 0 | 0 | — |
case-22 | pass→pass | 6,912 | 10,822 | +57% | 1 | 1 | 0% | 1,156 | 4,104 | +255% | 0 | 0 | — |
case-23 | pass→pass | 12,414 | 12,010 | -3% | 1 | 1 | 0% | 2,319 | 4,409 | +90% | 0 | 0 | — |
case-24 | pass→pass | 8,387 | 7,338 | -13% | 1 | 1 | 0% | 1,579 | 3,728 | +136% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.