skills/akires47/agent-skills/dotnet-best-practices

dotnet-best-practices

SKILL.md

.NET Best Practices

Comprehensive development guide for modern .NET applications with C# 12+. Contains 100+ rules across 11 categories, prioritized by impact to guide code generation, refactoring, and reviews.

When to Apply

Reference these guidelines when:

  • Writing new C# code or refactoring existing code
  • Designing APIs, services, or domain models
  • Implementing data access layers or async operations
  • Reviewing code for performance, correctness, or maintainability
  • Setting up dependency injection, configuration, or logging
  • Writing tests (unit, integration, snapshot)
  • Building distributed systems or microservices

Rule Categories by Priority

Priority Category Impact Prefix Rules
1 Error Handling CRITICAL error- 8
2 Async Patterns CRITICAL async- 9
3 Type Design HIGH type- 10
4 Database Performance HIGH db- 12
5 API Design MEDIUM-HIGH api- 8
6 Dependency Injection MEDIUM di- 7
7 Architecture MEDIUM arch- 8
8 Serialization MEDIUM serial- 6
9 Performance LOW-MEDIUM perf- 12
10 Logging LOW-MEDIUM log- 6
11 Testing LOW test- 8

Total: 94 Rules

Quick Reference

1. Error Handling (CRITICAL)

  • error-result-pattern - Return Result for expected errors, not exceptions
  • error-validation-boundaries - Validate at handler entry points, fail fast
  • error-guard-clauses - Use guard clauses for null/range checks
  • error-exception-filters - Use catch when for selective exception handling
  • error-global-handlers - Implement middleware for unhandled exceptions
  • error-never-catch-all - Avoid catching all exceptions without rethrowing
  • error-custom-exceptions - Use built-in exceptions, avoid custom ones
  • error-exception-properties - Add context to exceptions via Data property

2. Async Patterns (CRITICAL)

  • async-cancellation-token - Always accept CancellationToken in async methods
  • async-all-the-way - Never block on async code with .Result or .Wait()
  • async-valuetask - Use ValueTask for hot paths with synchronous completions
  • async-iasyncenumerable - Use IAsyncEnumerable for streaming data
  • async-parallel-foreach - Use Parallel.ForEachAsync for bounded concurrency
  • async-channel - Use System.Threading.Channels for producer-consumer
  • async-semaphore - Use SemaphoreSlim for rate limiting
  • async-configureawait - Use ConfigureAwait(false) in library code
  • async-avoid-async-void - Never use async void except for event handlers

3. Type Design (HIGH)

  • type-records-for-dtos - Use records for immutable DTOs
  • type-readonly-record-struct - Use readonly record struct for value objects
  • type-seal-classes - Seal classes by default unless designed for inheritance
  • type-composition-over-inheritance - Prefer composition over inheritance
  • type-nullable-reference-types - Enable nullable reference types, handle nulls explicitly
  • type-pattern-matching - Use switch expressions and pattern matching
  • type-primary-constructors - Use primary constructors for simple classes
  • type-immutability-default - Design types to be immutable by default
  • type-explicit-conversions - Avoid implicit conversions for value objects
  • type-no-public-setters - Use init or constructor, not public setters

4. Database Performance (HIGH)

  • db-read-write-separation - Separate read models from write models
  • db-notracking-default - Configure NoTracking by default in EF Core
  • db-row-limits - Always apply row limits to queries
  • db-n-plus-one - Avoid N+1 queries with Include or batch fetching
  • db-cartesian-explosion - Use AsSplitQuery to prevent Cartesian products
  • db-compiled-queries - Use EF.CompileAsyncQuery for hot paths
  • db-connection-pooling - Use NpgsqlDataSource for connection pooling
  • db-optimistic-concurrency - Use RowVersion for concurrent updates
  • db-projections-over-entities - Use Select projections, not full entities
  • db-no-application-joins - Do joins in SQL, never in application code
  • db-bulk-operations - Use ExecuteUpdate/Delete for bulk modifications
  • db-no-generic-repositories - Use purpose-built stores, not generic repositories

5. API Design (MEDIUM-HIGH)

  • api-accept-abstractions - Accept IEnumerable/IReadOnlyCollection in parameters
  • api-return-specific-types - Return IReadOnlyList/IReadOnlyCollection from methods
  • api-readonly-collections - Return immutable collections from public APIs
  • api-method-overloads - Use overloads for common scenarios, not optionals
  • api-extension-methods - Use extension methods for fluent APIs
  • api-fluent-builders - Use builder pattern for complex object construction
  • api-async-suffix - Suffix async methods with Async
  • api-avoid-out-params - Return tuples or records instead of out parameters

6. Dependency Injection (MEDIUM)

  • di-extension-methods - Group service registrations in extension methods
  • di-lifetime-management - Understand Singleton, Scoped, Transient lifetimes
  • di-options-pattern - Use IOptions for configuration
  • di-validate-on-start - Call ValidateOnStart() for configuration validation
  • di-keyed-services - Use keyed services for multiple implementations
  • di-avoid-service-locator - Never inject IServiceProvider as service locator
  • di-no-scoped-in-singleton - Never inject Scoped services into Singleton

7. Architecture (MEDIUM)

  • arch-vertical-slice - Organize by feature (vertical slices), not layers
  • arch-feature-organization - One feature per file with all related code
  • arch-static-handlers - Use static handler methods in features
  • arch-module-boundaries - Use InternalsVisibleTo for module boundaries
  • arch-feature-flags - Implement runtime feature toggling
  • arch-background-services - Use IHostedService for background work
  • arch-minimal-apis - Prefer minimal APIs over controller-based APIs
  • arch-map-endpoints-per-feature - Each feature maps its own endpoints

8. Serialization (MEDIUM)

  • serial-system-text-json - Use System.Text.Json, not Newtonsoft.Json
  • serial-source-generators - Use JsonSerializerContext source generators
  • serial-protobuf - Use Protobuf for actor systems and event sourcing
  • serial-messagepack - Use MessagePack for high-performance scenarios
  • serial-wire-compatibility - Design for forward/backward compatibility
  • serial-no-type-names - Never embed type names in wire format

9. Performance (LOW-MEDIUM)

  • perf-span-and-memory - Use Span/Memory for buffer operations
  • perf-defer-enumeration - Don't materialize IEnumerable until necessary
  • perf-frozen-collections - Use FrozenDictionary/FrozenSet for static data
  • perf-string-interpolation - Use string interpolation, not concatenation
  • perf-list-capacity - Initialize List with capacity when size is known
  • perf-dictionary-trygetvalue - Use TryGetValue instead of ContainsKey + indexer
  • perf-array-pool - Use ArrayPool for temporary large buffers
  • perf-object-pool - Use ObjectPool for expensive-to-create objects
  • perf-compiled-regex - Use GeneratedRegex source generator
  • perf-static-pure-functions - Prefer static methods for pure functions
  • perf-stackalloc - Use stackalloc for small temporary buffers
  • perf-avoid-closure-allocations - Cache delegates to avoid closure allocations

10. Logging (LOW-MEDIUM)

  • log-structured-logging - Use structured logging with named parameters
  • log-logger-message-define - Use LoggerMessage.Define for high-performance logging
  • log-correlation-ids - Implement request correlation via Activity.Current
  • log-activity-tracing - Use System.Diagnostics.Activity for distributed tracing
  • log-log-levels - Use appropriate log levels (Trace/Debug/Info/Warning/Error/Critical)
  • log-avoid-string-interpolation - Pass message template, not interpolated strings

11. Testing (LOW)

  • test-testcontainers - Use TestContainers for integration tests, not mocks
  • test-snapshot-testing - Use Verify for snapshot testing APIs and outputs
  • test-arrange-act-assert - Follow AAA pattern for test structure
  • test-fakes-vs-mocks - Prefer hand-written fakes over mocking libraries
  • test-data-builders - Use builder pattern for complex test data
  • test-integration-webappfactory - Use WebApplicationFactory for API integration tests
  • test-one-assertion-per-test - Focus each test on a single assertion
  • test-descriptive-names - Use descriptive test names that explain the scenario

How to Use

Read individual rule files for detailed explanations and code examples:

rules/error-result-pattern.md
rules/async-cancellation-token.md
rules/db-notracking-default.md

Each rule file contains:

  • Brief explanation of why it matters
  • āŒ Incorrect code example with explanation
  • āœ… Correct code example with explanation
  • Additional context and references

Full Compiled Document

For the complete guide with all rules expanded inline: AGENTS.md

This document is optimized for LLM context loading and contains all rules with full code examples.

Anti-Patterns Summary

Common mistakes to avoid:

Anti-Pattern Rule
Throwing exceptions for business logic error-result-pattern
Blocking on async code with .Result async-all-the-way
Mutable DTOs with public setters type-records-for-dtos
Queries without row limits db-row-limits
N+1 query problems db-n-plus-one
Returning List from APIs api-readonly-collections
Massive Program.cs with 200+ DI registrations di-extension-methods
Organizing by technical layers arch-vertical-slice
Using Newtonsoft.Json serial-system-text-json
String concatenation in loops perf-string-interpolation
Mocking databases in tests test-testcontainers

Resources

Weekly Installs
1
First Seen
Jan 29, 2026
Installed on
opencode1
cursor1
claude-code1