$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

AI Coding Rules for C# Teams

C# evolves fast — but AI assistants still generate pre-.NET 6 patterns. Rules for modern C#: records, top-level statements, minimal APIs, and nullable reference types.

8 min read·August 14, 2025

AI still generates .NET Framework patterns in 2026

Records, nullable references, minimal APIs, and modern C# 12 conventions

Why C# Needs Specific AI Coding Rules

C# has undergone a renaissance since .NET Core — top-level statements, minimal APIs, records, pattern matching, nullable reference types, primary constructors, and collection expressions. But AI assistants trained on decades of C# code default to the verbose, ceremony-heavy patterns that dominated the .NET Framework era.

The most common AI failures in C#: generating full Program.cs with explicit Main method instead of top-level statements, using traditional MVC controllers when minimal APIs would be simpler, ignoring nullable reference types (NRTs), creating verbose DTOs instead of records, and using old-style configuration instead of the Options pattern.

These rules target C# 12 / .NET 8+ projects. If you're on an older version, adjust the feature-specific rules accordingly — but keep the architectural and convention rules regardless.

Rule 1: Modern C# Features by Default

The rule: 'This project uses C# 12 / .NET 8. Use top-level statements in Program.cs. Use records for immutable data types (DTOs, events, value objects). Use primary constructors for classes with dependency injection. Use collection expressions ([1, 2, 3] syntax). Use raw string literals for multi-line text. Use file-scoped namespaces.'

For pattern matching: 'Use switch expressions instead of switch statements where the result is assigned to a variable. Use is pattern matching for type checks instead of casting with as. Use property patterns for complex conditions: if (user is { Role: "admin", IsActive: true }).'

The version declaration is critical. 'C# 12 / .NET 8' in the first line of your rule file prevents the AI from generating backward-compatible code that misses modern features.

Rule 2: Nullable Reference Types

Nullable reference types (NRTs) are C#'s biggest safety improvement since generics, but AI assistants frequently generate code that ignores them — returning null without the ? annotation, or using the null-forgiving operator (!) to suppress warnings instead of properly handling nullability.

The rule: 'Nullable reference types are enabled. All reference types are non-null by default. Use string? (nullable) only when null is a valid value. Never use the null-forgiving operator (!) — if the compiler warns about null, handle it properly. Use the null-conditional operator (?.) and null-coalescing operator (??) for safe navigation.'

For API boundaries: 'All method parameters that could be null must be declared nullable (string? name). Validate non-null parameters with ArgumentNullException.ThrowIfNull() at the method entry point. Return types should be non-null unless null genuinely represents "no value" — prefer returning empty collections over null.'

⚠️ Never Use !

The null-forgiving operator (!) tells the compiler 'trust me, this isn't null.' It's almost always wrong. If the compiler warns about null, handle it properly with ?., ??, or a null check.

Rule 3: ASP.NET Minimal APIs and Controllers

AI assistants default to full MVC controllers with attribute routing because that's what dominates their training data. For many endpoints — especially CRUD operations and simple data access — minimal APIs are cleaner and more concise.

The rule: 'Use minimal APIs (app.MapGet, app.MapPost) for simple endpoints with straightforward request/response patterns. Use controllers for endpoints that need model binding, complex validation, or multiple action results. Group minimal API endpoints using route groups (app.MapGroup). Use TypedResults for strongly-typed responses.'

For the Options pattern: 'Use IOptions<T> for configuration injection, not raw IConfiguration. Define configuration sections as records. Register with builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("MySection")). Never read configuration values directly from IConfiguration in services.'

  • Minimal APIs for simple CRUD — app.MapGet/Post/Put/Delete with route groups
  • Controllers for complex endpoints — model binding, validation, multiple responses
  • TypedResults for strongly-typed responses — Results.Ok(data), Results.NotFound()
  • IOptions<T> for configuration — never raw IConfiguration in services
  • Middleware pipeline order: auth before routing before endpoints

Rule 4: Entity Framework Core Patterns

AI assistants generate EF Core code that works but violates conventions the EF team recommends. Common issues: lazy loading everywhere (performance disaster), not using navigation properties correctly, raw SQL when LINQ works, and forgetting to configure relationships in OnModelCreating.

The rule: 'Use EF Core with eager loading (Include/ThenInclude) or explicit loading — never lazy loading in web applications. Configure relationships in OnModelCreating with Fluent API, not data annotations. Use migrations for all schema changes (dotnet ef migrations add). Use AsNoTracking() for read-only queries. Use ExecuteUpdateAsync/ExecuteDeleteAsync for bulk operations.'

For repositories: 'Do not create a generic repository abstraction over EF Core — DbContext is already a unit of work. Inject DbContext directly into services. Use IQueryable extension methods for reusable query filters.'

💡 Skip the Repository

Don't create a generic repository abstraction over EF Core — DbContext is already a unit of work and repository. Inject DbContext directly into services and use IQueryable extensions for reusable filters.

Rule 5: Async Patterns in C#

C#'s async/await is mature and well-designed, but AI assistants still generate patterns that cause deadlocks or poor performance: calling .Result or .Wait() on tasks, not using ConfigureAwait correctly in library code, and creating unnecessary async state machines.

The rule: 'All I/O operations must be async. Never call .Result or .Wait() on a Task — it causes deadlocks in ASP.NET. Use await consistently through the call chain. Use ValueTask<T> for methods that often complete synchronously. In library code, use ConfigureAwait(false) on all awaits. Use async streams (IAsyncEnumerable<T>) for streaming data.'

For cancellation: 'Accept CancellationToken as the last parameter in all async methods. Pass the token through to all downstream async calls. Check token.ThrowIfCancellationRequested() in long-running loops. In ASP.NET, use HttpContext.RequestAborted as the cancellation token.'

ℹ️ Deadlock Prevention

Never call .Result or .Wait() on a Task in ASP.NET — it causes deadlocks. Use await consistently through the entire call chain. This is the #1 async bug AI generates in C#.

Complete C# Rules Template

Consolidated template for C# 12 / .NET 8 teams. Covers modern features, nullability, ASP.NET, EF Core, and async patterns.

  • C# 12 / .NET 8: records, primary constructors, collection expressions, file-scoped namespaces
  • Nullable reference types enabled — no null-forgiving (!), use ?. and ?? operators
  • Minimal APIs for simple endpoints — controllers for complex scenarios
  • IOptions<T> for configuration — never raw IConfiguration in services
  • EF Core: eager loading, Fluent API config, AsNoTracking for reads, no lazy loading
  • Async everywhere: no .Result or .Wait(), CancellationToken as last parameter
  • xUnit + FluentAssertions for testing — [Fact], [Theory], [InlineData]
  • MediatR for CQRS — commands/queries through mediator, not direct service calls