wshaddix/dotnet-skills6 files

Modern Csharp Coding Standards

Write modern, high-performance C# code using records, pattern matching, value objects, async/await, Span<T>/Memory<T>, and best-practice API design patterns. Emphasizes functional-style programming with C# 12+ features. Use when writing new C# code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.

Specification
Skill ID
wshaddix/dotnet-skills/modern-csharp-coding-standards
Publisher
wshaddix
Repository
dotnet-skills
Installs
0
Files
6
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

wshaddix/dotnet-skills/modern-csharp-coding-standardsInstalls these files
  • SKILL.md
  • reference/anti-patterns.md
  • reference/api-design.md
  • reference/error-handling.md
  • reference/language-patterns.md
  • reference/performance-patterns.md

What this skill tells the agent

Modern C# Coding Standards

When to Use This Skill

Use this skill when:

  • Writing new C# code or refactoring existing code
  • Designing public APIs for libraries or services
  • Optimizing performance-critical code paths
  • Implementing domain models with strong typing
  • Building async/await-heavy applications
  • Working with binary data, buffers, or high-throughput scenarios

Core Principles

  1. Immutability by Default - Use record types and init-only properties
  2. Type Safety - Leverage nullable reference types and value objects
  3. Modern Pattern Matching - Use switch expressions and patterns extensively
  4. Async Everywhere - Prefer async APIs with proper cancellation support
  5. Zero-Allocation Patterns - Use Span<T> and Memory<T> for performance-critical code
  6. API Design - Accept abstractions, return appropriately specific types
  7. Composition Over Inheritance - Avoid abstract base classes, prefer composition
  8. Value Objects as Structs - Use readonly record struct for value objects

Naming Conventions

General Rules

ElementConventionExample
NamespacesPascalCase, dot-separatedMyCompany.MyProduct.Core
Classes, Records, StructsPascalCaseOrderService, OrderSummary
InterfacesI + PascalCaseIOrderRepository
MethodsPascalCaseGetOrderAsync
PropertiesPascalCaseOrderDate
EventsPascalCaseOrderCompleted
Public constantsPascalCaseMaxRetryCount
Private fields_camelCase_orderRepository
Parameters, localscamelCaseorderId, totalAmount
Type parametersT or T + PascalCaseT, TKey, TValue
Enum membersPascalCaseOrderStatus.Pending

Async Method Naming

Suffix async methods with Async:

public Task<Order> GetOrderAsync(int id);
public ValueTask SaveChangesAsync(CancellationToken ct);

Exception: Event handlers and interface implementations where the framework does not use the `Async` suffix (e.g., ASP.NET Core middleware `InvokeAsync` is already named by the framework).

Boolean Naming

Prefix booleans with is, has, can, should, or similar:

public bool IsActive { get; set; }
public bool HasOrders { get; }
public bool CanDelete(Order order);

Collection Naming

Use plural nouns for collections:

public IReadOnlyList<Order> Orders { get; }
public Dictionary<string, int> CountsByName { get; }

File Organization

One Type Per File

Each top-level type (class, record, struct, interface, enum) should be in its own file, named exactly as the type. Nested types stay in the containing type's file.

OrderService.cs        -> public class OrderService
IOrderRepository.cs    -> public interface IOrderRepository
OrderStatus.cs         -> public enum OrderStatus
OrderSummary.cs        -> public record OrderSummary

File-Scoped Namespaces

Always use file-scoped namespaces (C# 10+):

namespace MyApp.Services;

public class OrderService { }

Using Directives

Place using directives at the top of the file, outside the namespace. With <ImplicitUsings>enable</ImplicitUsings> (default in modern .NET), common namespaces are already imported.

Order of using directives:

  1. System.* namespaces
  2. Third-party namespaces
  3. Project namespaces

Code Style

Braces

Always use braces for control flow, even for single-line bodies:

if (order.IsValid)
{
    Process(order);
}

Expression-Bodied Members

Use expression bodies for single-expression members:

public string FullName => $"{FirstName} {LastName}";
public override string ToString() => $"Order #{Id}";

var Usage

Use var when the type is obvious from the right-hand side:

var orders = new List<Order>();
var customer = GetCustomerById(id);

IOrderRepository repo = serviceProvider.GetRequiredService<IOrderRepository>();
decimal total = CalculateTotal(items);

Null Handling

Prefer pattern matching over null checks:

if (order is not null) { }
if (order is { Status: OrderStatus.Active }) { }

var name = customer?.Name ?? "Unknown";
var orders = customer?.Orders ?? [];
items ??= [];

String Handling

Prefer string interpolation over concatenation or string.Format:

var message = $"Order {orderId} totals {total:C2}";

var json = $$"""
    {
        "id": {{orderId}},
        "name": "{{name}}"
    }
    """;

Access Modifiers

Always specify access modifiers explicitly. Do not rely on defaults:

public class OrderService
{
    private readonly IOrderRepository _repo;
    internal void ProcessBatch() { }
}

Modifier Order

access (public/private/protected/internal) -> static -> extern -> new ->
virtual/abstract/override/sealed -> readonly -> volatile -> async -> partial
public static readonly int MaxSize = 100;
protected virtual async Task<Order> LoadAsync() => await repo.GetDefaultAsync();
public sealed override string ToString() => Name;

Type Design

Seal Classes by Default

Seal classes that are not designed for inheritance. This improves performance (devirtualization) and communicates intent:

public sealed class OrderService(IOrderRepository repo)
{
}

Only leave classes unsealed when you explicitly design them as base classes.

Prefer Composition Over Inheritance

public sealed class OrderProcessor(IValidator validator, INotifier notifier)
{
    public async Task ProcessAsync(Order order)
    {
        await validator.ValidateAsync(order);
        await notifier.NotifyAsync(order);
    }
}

Interface Segregation

Keep interfaces focused. Prefer multiple small interfaces over one large one:

public interface IOrderReader
{
    Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);
    Task<IReadOnlyList<Order>> GetAllAsync(CancellationToken ct = default);
}

public interface IOrderWriter
{
    Task<Order> CreateAsync(Order order, CancellationToken ct = default);
    Task UpdateAsync(Order order, CancellationToken ct = default);
}

Language Patterns

See Language Patterns for detailed guidance on:

  • Records for Immutable Data (C# 9+)
  • Value Objects as readonly record struct
  • Pattern Matching (C# 8-12)
  • Nullable Reference Types (C# 8+)
  • Composition Over Inheritance

Performance Patterns

See Performance Patterns for detailed guidance on:

  • Async/Await Best Practices
  • Span<T> and Memory<T> for Zero-Allocation Code

API Design Principles

See API Design Principles for detailed guidance on:

  • Accept Abstractions, Return Appropriately Specific
  • Method Signatures Best Practices

Error Handling

See Error Handling for detailed guidance on:

  • Result Type Pattern (Railway-Oriented Programming)

Testing Patterns

public record OrderBuilder
{
    public OrderId Id { get; init; } = OrderId.New();
    public CustomerId CustomerId { get; init; } = CustomerId.New();
    public Money Total { get; init; } = new Money(100m, "USD");
    public IReadOnlyList<OrderItem> Items { get; init; } = Array.Empty<OrderItem>();

    public Order Build() => new(Id, CustomerId, Total, Items);
}

[Fact]
public void CalculateDiscount_LargeOrder_AppliesCorrectDiscount()
{
    var baseOrder = new OrderBuilder().Build();
    var largeOrder = baseOrder with { Total = new Money(1500m, "USD") };

    var discount = _service.CalculateDiscount(largeOrder);

    discount.Should().Be(new Money(225m, "USD"));
}

[Theory]
[InlineData("ORD-12345", true)]
[InlineData("INVALID", false)]
public void TryParseOrderId_VariousInputs_ReturnsExpectedResult(
    string input, bool expected)
{

Use this skill in a project

Open any RiverX project, search the Skills panel for wshaddix/dotnet-skills/modern-csharp-coding-standards, and install it in one click.

Use this skill