csharp-coding-standards
Skill17 starsChanged 6 days agoMITView on GitHub
Use this skill when writing, reviewing, or refactoring modern C# and .NET code, designing APIs, optimizing performance with Span<T>/Memory<T>, implementing domain models with records and value objects, or working with async patterns and pattern matching.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- linuxchata/ai-playbooktheir own version, first seen May 9, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- linuxchata/ai-playbook★ 6same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository9 other files it gives its agents.
The file
Show its rules and text
We split this skill into the rules it gives an agent
36 rules. About 0 are common, 17 are distinctive, and 19 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈17
Distinctive · estimate
This author's own choices. Worth a look.
19
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 10
Use this skill when:
- Very specific: line 27
Immutability by Default: - Use record types and init-only properties
- Very specific: line 29
Modern Pattern Matching: - Use switch expressions and patterns extensively
- Distinctive: line 30
Async Everywhere: - Prefer async APIs with proper cancellation support
- Very specific: line 31
Zero-Allocation Patterns: - Use Span<T> and Memory<T> for performance-critical code
- Distinctive: line 33
Composition Over Inheritance: - Avoid abstract base classes, prefer composition
- Very specific: line 34
Value Objects as Structs: - Use readonly record struct for value objects
- Distinctive: line 36
Consistent Naming: - Follow .NET naming conventions for types, methods, and properties
- Distinctive: line 37
Consistent Formatting: - Follow consistent code formatting and indentation rules
- Very specific: line 45
Use record types for DTOs, messages, events, and domain entities.
- Distinctive: line 106
Use switch expressions, property patterns, relational patterns, and list patterns for cleaner code.
- Distinctive: line 161
Avoid abstract base classes: Use interfaces + composition.
- Very specific: line 201
Always accept CancellationToken with = default
- Very specific: line 202
Use ConfigureAwait(false) in library code
- Very specific: line 203
Never block on async code (no .Result or .Wait())
- Distinctive: line 204
Use linked CancellationTokenSource for timeouts
- Very specific: line 208
Use Span<T> for synchronous zero-allocation operations, Memory<T> for async, and ArrayPool<T> for large temporary buffers.
- Very specific: line 288
Use record for DTOs, messages, and domain entities
- Very specific: line 289
Use readonly record struct for value objects
- Distinctive: line 294
Use async/await for all I/O operations
- Very specific: line 296
Use Span<T> and Memory<T> for high-performance scenarios
- Very specific: line 298
Use Result<T, TError> for expected errors
- Distinctive: line 300
Prefer composition over inheritance
- Distinctive: line 303
Don't use mutable classes when records work
- Very specific: line 304
Don't use classes for value objects (use readonly record struct)
- Distinctive: line 305
Don't create deep inheritance hierarchies
- Distinctive: line 306
Don't ignore nullable reference type warnings
- Distinctive: line 307
Don't add unnecessary null checks on non-nullable symbols
- Very specific: line 308
Don't specify fully qualified or partially qualified namespace prefixes inline on types (e.g., Core.Analysis.ArchitectureQueryService or …
- Distinctive: line 309
Don't re-declare imports already provided by global or implicit usings
- Very specific: line 310
Don't block on async code (.Result, .Wait())
- Very specific: line 311
Don't use byte[] when Span<byte> suffices
- Very specific: line 312
Don't forget CancellationToken parameters
- Distinctive: line 313
Don't return mutable collections from APIs
- Distinctive: line 314
Don't throw exceptions for expected business errors
- Very specific: line 315
Don't allocate large arrays repeatedly (use ArrayPool)
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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
Reference Files
- value-objects-and-patterns.md: Full value object examples and pattern matching code
- performance-and-api-design.md: Span
<T>/Memory<T>examples and API design principles - composition-and-error-handling.md: Composition over inheritance, Result type, testing patterns
- anti-patterns-and-reflection.md: Reflection avoidance and common anti-patterns
Core Principles
- Immutability by Default - Use
recordtypes andinit-only properties - Type Safety & Nullability - Leverage nullable reference types (respect non-nullable contracts without redundant defensive null checks) 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 - Namespace Hygiene & Short Type Names - Shorten namespaces, never use inline qualified type names (e.g., never write
Core.Analysis.ArchitectureQueryService,Parser.WorkspaceIndexer, orCodeExplorer.Server.CodeExplorerServerHostin code body; always use the short type nameArchitectureQueryServiceand import the namespace viausingor global<Using>), and leverage global/implicit usings. Ensure that namespaces reflect the logical structure of the project. - Consistent Naming - Follow .NET naming conventions for types, methods, and properties
- Consistent Formatting - Follow consistent code formatting and indentation rules
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 propertiesrecord 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.
public decimal CalculateDiscount(Order order) => order switch
{
{ Total: > 1000m } => order.Total * 0.15m,
{ Total: > 500m } => order.Total * 0.10m,
{ Total: > 100m } => order.Total * 0.05m,
_ => 0m
};See value-objects-and-patterns.md for full pattern matching examples.
Nullable Reference Types (C# 8+)
Enable nullable reference types in your project and handle nulls explicitly.
// In .csproj
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Explicit nullability
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// Pattern matching with null checks
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// Guard clauses with ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// order is now non-nullable in this scope
Console.WriteLine(order.Id);
}Composition Over Inheritance
Avoid abstract base classes. Use interfaces + composition. Use static helpers for shared logic. Use records with factory methods for variants.
See composition-and-error-handling.md for full examples.
Performance Patterns
Async/Await Best Practices
// Async all the way - always accept CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// ValueTask for frequently-called, often-synchronous methods
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(orderId, out var order))
return ValueTask.FromResult<Order?>(order);
return GetFromDatabaseAsync(orderId, cancellationToken);
}
// IAsyncEnumerable for streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
string customerId,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var order in _repository.StreamAllAsync(cancellationToken))
{
if (order.CustomerId == customerId)
yield return order;
}
}Key rules:
- Always accept
CancellationTokenwith= default - Use
ConfigureAwait(false)in library code - Never block on async code (no
.Resultor.Wait()) - Use linked CancellationTokenSource for timeouts
Span<T> and Memory<T>
Use Span<T> for synchronous zero-allocation operations, Memory<T> for async, and ArrayPool<T> for large temporary buffers.
See performance-and-api-design.md for complete Span/Memory examples and the API design section.
Error Handling: Result Type
For expected errors, use Result<T, TError> instead of exceptions. Use exceptions only for unexpected/system errors.
See composition-and-error-handling.md for the full Result type implementation and usage examples.
Avoid Reflection-Based Metaprogramming
Banned: AutoMapper, Mapster, ExpressMapper. Use explicit mapping extension methods instead. Use UnsafeAccessorAttribute (.NET 8+) when you genuinely need private member access.
See anti-patterns-and-reflection.md for full guidance.
Code Organization
// 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
recordfor DTOs, messages, and domain entities - Use
readonly record structfor value objects - Leverage pattern matching with
switchexpressions - Enable and respect nullable reference types (treat non-nullable types as guaranteed non-null)
- Shorten namespaces and omit redundant type qualifications
- Rely on global/implicit usings and omit unnecessary file-level imports
- 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>) - Use
Result<T, TError>for expected errors - Pool buffers with
ArrayPool<T>for large allocations - Prefer composition over inheritance
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 add unnecessary null checks on non-nullable symbols
- Don't specify fully qualified or partially qualified namespace prefixes inline on types (e.g.,
Core.Analysis.ArchitectureQueryServiceorParser.WorkspaceIndexer); always import the namespace viausing/<Using>and reference the short type name (ArchitectureQueryService) - Don't re-declare imports already provided by global or implicit usings
- 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 allocate large arrays repeatedly (use
ArrayPool)
See anti-patterns-and-reflection.md for detailed anti-pattern examples.
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/
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

