Claude
Skills
Sign in
Back

modern-csharp-coding-standards

Included with Lifetime
$97 forever

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.

Design

What this skill does


# 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

| Element | Convention | Example |
|---------|-----------|---------|
| Namespaces | PascalCase, dot-separated | `MyCompany.MyProduct.Core` |
| Classes, Records, Structs | PascalCase | `OrderService`, `OrderSummary` |
| Interfaces | `I` + PascalCase | `IOrderRepository` |
| Methods | PascalCase | `GetOrderAsync` |
| Properties | PascalCase | `OrderDate` |
| Events | PascalCase | `OrderCompleted` |
| Public constants | PascalCase | `MaxRetryCount` |
| Private fields | `_camelCase` | `_orderRepository` |
| Parameters, locals | camelCase | `orderId`, `totalAmount` |
| Type parameters | `T` or `T` + PascalCase | `T`, `TKey`, `TValue` |
| Enum members | PascalCase | `OrderStatus.Pending` |

### Async Method Naming

Suffix async methods with `Async`:

```csharp
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:

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

### Collection Naming

Use plural nouns for collections:

```csharp
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+):

```csharp
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:

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

### Expression-Bodied Members

Use expression bodies for single-expression members:

```csharp
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:

```csharp
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:

```csharp
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`:

```csharp
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:

```csharp
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
```

```csharp
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:

```csharp
public sealed class OrderService(IOrderRepository repo)
{
}
```

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

### Prefer Composition Over Inheritance

```csharp
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:

```csharp
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](./reference/language-patterns.md) 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](./reference/performance-patterns.md) for detailed guidance on:
- Async/Await Best Practices
- Span<T> and Memory<T> for Zero-Allocation Code

---

## API Design Principles

See [API Design Principles](./reference/api-design.md) for detailed guidance on:
- Accept Abstractions, Return Appropriately Specific
- Method Signatures Best Practices

---

## Error Handling

See [Error Handling](./reference/error-handling.md) for detailed guidance on:
- Result Type Pattern (Railway-Oriented Programming)

---

## Testing Patterns

```csharp
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)
{
    var result = OrderIdParser

Related in Design