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.
- Skill ID
- wshaddix/dotnet-skills/modern-csharp-coding-standards
- Publisher
- wshaddix
- Repository
- dotnet-skills
- Installs
- 0
- Files
- 6
- Synced
- Sep 23, 2026
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
- Immutability by Default - Use
recordtypes andinit-only properties - Type Safety - Leverage nullable reference types and value objects
- Modern Pattern Matching - Use
switchexpressions and patterns extensively - Async Everywhere - Prefer async APIs with proper cancellation support
- Zero-Allocation Patterns - Use
Span<T>andMemory<T>for performance-critical code - API Design - Accept abstractions, return appropriately specific types
- Composition Over Inheritance - Avoid abstract base classes, prefer composition
- Value Objects as Structs - Use
readonly record structfor 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:
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 OrderSummaryFile-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:
System.*namespaces- Third-party namespaces
- 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 -> partialpublic 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)
{More from wshaddix
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.
