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.
SKILL.md
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
Immutability by Default - Use record types and init-only properties
Type Safety - Leverage nullable reference types and value objects
Modern Pattern Matching - Use switch expressions and patterns extensively
Async Everywhere - Prefer async APIs with proper cancellation support
Zero-Allocation Patterns - Use Span<T> and Memory<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 for value objects
readonly record struct
Language Patterns
Records for Immutable Data (C# 9+)
Use record types for DTOs, messages, events, and domain entities.
// Simple immutable DTO
public record CustomerDto(string Id, string Name, string Email);
// Record with validation in constructor
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(value));
Value = value;
}
}
// Records with collections - use IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
When to use record class vs record struct:
record class (default): Reference types, use for entities, aggregates, DTOs with multiple properties
record struct: Value types, use for value objects (see next section)
Value Objects as readonly record struct
Value objects should always be readonly record struct for performance and value semantics. Use explicit conversions, never implicit operators.
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId cannot be empty", nameof(value)))
{ }
public override string ToString() => Value;
}
public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
See value-objects-and-patterns.md for complete examples including multi-value objects, factory patterns, and the no-implicit-conversion rule.
Pattern Matching (C# 8-12)
Use switch expressions, property patterns, relational patterns, and list patterns for cleaner code.
Banned: AutoMapper, Mapster, ExpressMapper. Use explicit mapping extension methods instead. Use UnsafeAccessorAttribute (.NET 8+) when you genuinely need private member access.
// File: Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. Primary domain type
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 });
}
}
// 2. Enums for state
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. Related types
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. Value objects
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. Errors
public readonly record struct OrderError(string Code, string Message);
Best Practices Summary
DO's
Use record for DTOs, messages, and domain entities
Use readonly record struct for value objects
Leverage pattern matching with switch expressions
Enable and respect nullable reference types
Use async/await for all I/O operations
Accept CancellationToken in all async methods
Use Span<T> and Memory<T> for high-performance scenarios