
modern-csharp-coding-standards
Write modern, high-performance C code using records, pattern matching, value objects, async/await, Span /Memory , 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.
- Installs
- 583
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/wshaddix/dotnet-skills --skill modern-csharp-coding-standards- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads modern-csharp-coding-standards automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
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 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:
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 -> 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)
{
var result = OrderIdParser.TryParse(input.AsSpan(), out var orderId);
result.Should().Be(expected);
}
[Fact]
public void Money_Add_SameCurrency_ReturnsSum()
{
var money1 = new Money(100m, "USD");
var money2 = new Money(50m, "USD");
var result = money1.Add(money2);
result.Should().Be(new Money(150m, "USD"));
}
[Fact]
public void Money_Add_DifferentCurrency_ThrowsException()
{
var usd = new Money(100m, "USD");
var eur = new Money(50m, "EUR");
var act = () => usd.Add(eur);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*different currencies*");
}
CancellationToken Conventions
Accept CancellationToken as the last parameter in async methods. Use default as the default value for optional tokens:
public async Task<Order> GetOrderAsync(int id, CancellationToken ct = default)
{
return await _repo.GetByIdAsync(id, ct);
}
Always forward the token to downstream async calls. Never ignore a received CancellationToken.
XML Documentation
Add XML docs to public API surfaces. Keep them concise:
/// <summary>
/// Retrieves an order by its unique identifier.
/// </summary>
/// <param name="id">The order identifier.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The order, or <see langword="null"/> if not found.</returns>
public Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);
Do not add XML docs to:
- Private or internal members (unless it's a library's
InternalsVisibleToAPI) - Self-evident members (e.g.,
public string Name { get; }) - Test methods
Avoid Reflection-Based Metaprogramming
See Anti-Patterns for detailed guidance on:
- Why to avoid AutoMapper, Mapster, and similar reflection-based libraries
- Using explicit mapping methods instead
- UnsafeAccessorAttribute for legitimate reflection needs
Anti-Patterns to Avoid
See Anti-Patterns for detailed guidance on:
- Mutable DTOs
- Classes for value objects
- Deep inheritance hierarchies
- Exposing mutable collections
- Forgetting CancellationToken
- Blocking on async code
Code Organization
namespace MyApp.Domain.Orders;
public record Order(
OrderId Id,
CustomerId CustomerId,
Money Total,
OrderStatus Status,
IReadOnlyList<OrderItem> Items
)
{
public bool IsCompleted => Status is OrderStatus.Completed;
public Result<Order, OrderError> AddItem(OrderItem item)
{
if (Status is not OrderStatus.Draft)
return Result<Order, OrderError>.Failure(
new OrderError("ORDER_NOT_DRAFT", "Can only add items to draft orders"));
var newItems = Items.Append(item).ToList();
var newTotal = new Money(
Items.Sum(i => i.Total.Amount) + item.Total.Amount,
Total.Currency);
return Result<Order, OrderError>.Success(
this with { Items = newItems, Total = newTotal });
}
}
public enum OrderStatus
{
Draft,
Submitted,
Processing,
Completed,
Cancelled
}
public record OrderItem(
ProductId ProductId,
Quantity Quantity,
Money UnitPrice
)
{
public Money Total => new(
UnitPrice.Amount * Quantity.Value,
UnitPrice.Currency);
}
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
public readonly record struct OrderError(string Code, string Message);
Analyzer Enforcement
Configure these analyzers in Directory.Build.props or .editorconfig to enforce standards automatically:
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-all</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
Key .editorconfig rules for C# style:
[*.cs]
csharp_style_namespace_declarations = file_scoped:warning
csharp_prefer_braces = true:warning
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_style_require_accessibility_modifiers = always:warning
csharp_style_prefer_pattern_matching = true:suggestion
Best Practices Summary
DO's
- Use
recordfor DTOs, messages, and domain entities - Use
readonly record structfor value objects - Leverage pattern matching with
switchexpressions - Enable and respect nullable reference types
- Use async/await for all I/O operations
- Accept
CancellationTokenin all async methods - Use
Span<T>andMemory<T>for high-performance scenarios - Accept abstractions (
IEnumerable<T>,IReadOnlyList<T>) - Return appropriate interfaces or concrete types
- Use
Result<T, TError>for expected errors - Use
ConfigureAwait(false)in library code - Pool buffers with
ArrayPool<T>for large allocations - Prefer composition over inheritance
- Avoid abstract base classes in application code
DON'Ts
- Don't use mutable classes when records work
- Don't use classes for value objects (use
readonly record struct) - Don't create deep inheritance hierarchies
- Don't ignore nullable reference type warnings
- Don't block on async code (
.Result,.Wait()) - Don't use
byte[]whenSpan<byte>suffices - Don't forget
CancellationTokenparameters - Don't return mutable collections from APIs
- Don't throw exceptions for expected business errors
- Don't use
stringconcatenation in loops - Don't allocate large arrays repeatedly (use
ArrayPool)
Knowledge Sources
Conventions in this skill are grounded in publicly available content from:
- Microsoft Framework Design Guidelines -- The canonical reference for .NET naming, type design, and API surface conventions. Source: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/
- C# Language Design Notes (Mads Torgersen et al.) -- Design rationale behind C# language features that affect coding standards. Key decisions relevant to this skill: file-scoped namespaces (reducing nesting for readability), pattern matching over type checks (expressiveness),
requiredmembers (compile-time initialization safety), andvarusage guidelines (readability-first). Source: https://github.com/dotnet/csharplang/tree/main/meetings
Additional Resources
- C# Language Specification: https://learn.microsoft.com/en-us/dotnet/csharp/
- Pattern Matching: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
- Span<T> and Memory<T>: https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/
- Async Best Practices: https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
- .NET Performance Tips: https://learn.microsoft.com/en-us/dotnet/framework/performance/
- C# Coding Conventions: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions
- C# Identifier Naming Rules: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names
- .editorconfig for .NET: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options
FAQ
What does the modern-csharp-coding-standards skill do?
Write modern, high-performance C code using records, pattern matching, value objects, async/await, Span /Memory , 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.
How do I install modern-csharp-coding-standards?
Run npx skills add https://github.com/wshaddix/dotnet-skills --skill modern-csharp-coding-standards in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/modern-csharp-coding-standards/ (personal) or .claude/skills/modern-csharp-coding-standards/ (project).
Which AI agents can use modern-csharp-coding-standards?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is modern-csharp-coding-standards safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains modern-csharp-coding-standards?
It is published by wshaddix in wshaddix/dotnet-skills. It has 583 recorded installs.
More from wshaddix/dotnet-skills
dotnet-xunit
wshaddix/dotnet-skills
Writing xUnit tests. v3 Fact/Theory, fixtures, parallelism, IAsyncLifetime, v2 compatibility.
dotnet-testing-strategy
wshaddix/dotnet-skills
Deciding how to test .NET code. Unit vs integration vs E2E decision tree, test doubles.
csharp-wolverinefx
wshaddix/dotnet-skills
Build .NET applications with WolverineFX for messaging, HTTP services, and event sourcing. Use when implementing command handlers, message handlers, HTTP endpoints with WolverineFx.HTTP, transactional outbox patterns, event sourcing with Marten, CQRS architectures, cascading messages, batch message processing, or configuring transports like RabbitMQ, Azure Service Bus, or Amazon SQS.
testcontainers
wshaddix/dotnet-skills
Patterns for using Testcontainers in .NET integration tests to spin up real dependencies like databases and message queues. Use when writing integration tests that require real databases, testing with message brokers like RabbitMQ or Kafka, or isolating test dependencies with Docker containers.
microsoft-extensions-dependency-injection
wshaddix/dotnet-skills
Dependency injection patterns and best practices using Microsoft.Extensions.DependencyInjection for .NET applications. Use when configuring DI containers in .NET, choosing between service lifetimes (Singleton, Scoped, Transient), or implementing decorator patterns and service interception.
logging-observability
wshaddix/dotnet-skills
Production-grade logging and observability patterns for ASP.NET Core Razor Pages. Covers structured logging with Serilog, correlation IDs, health checks, request logging, OpenTelemetry integration, and diagnostic best practices. Use when setting up structured logging in ASP.NET Core applications, implementing distributed tracing with OpenTelemetry, or configuring health checks and observability.
Popular code quality & workflow skills
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
improve-codebase-architecture
mattpocock/skills
Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
handoff
mattpocock/skills
Compact the current conversation into a handoff document for another agent to pick up.
domain-modeling
mattpocock/skills
Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.
codebase-design
mattpocock/skills
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
diagnosing-bugs
mattpocock/skills
Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.