C# Coding Skill
Patterns and idioms for writing modern C# code.
This skill covers C# 14 language features, .NET 10 conventions, and idiomatic C# development. Load this skill before writing or modifying C# code.
Required Language Features
- •File-scoped namespaces:
namespace MyApp.Core; - •Primary constructors:
class Service(IDep dep, ILogger logger) - •Collection expressions (MANDATORY):
[],[item],[..spread]- •NEVER use
new[],new List<T>(),Array.Empty<T>() - •For type inference, prefer
[new T { }, new T { }]over casts - •Use
T[] x = [...]only when simpler forms fail
- •NEVER use
- •Records for DTOs,
initsetters - •Pattern matching:
is not null, switch expressions, property patterns- •Property patterns:
obj is Type { Prop: value }overobj is Type t && t.Prop == value - •Recursive/nested:
obj is Type { Outer: { Inner: value } } - •Extended property pattern:
obj is { Outer.Inner: value }(C# 10) - •Empty property pattern:
{ } namematches non-null and binds (e.g.,is Type { Prop: { } x })
- •Property patterns:
- •Spread operator for collections:
[..first, ..second]
C# 14 Features (.NET 10)
- •
fieldkeyword:public string Name { get; set => field = value ?? throw; } = ""; - •Extension blocks:
extension(T src) { public bool IsEmpty => !src.Any(); }(properties + statics) - •Null-conditional assignment:
obj?.Prop = value;(RHS evaluated only if obj not null) - •Lambda modifiers without types:
(text, out result) => int.TryParse(text, out result)
Migration: Use new syntax for new code; opportunistically refactor existing code when revisiting.
Required Idioms
Visibility
- •Use
internalfor implementation classes (CLI apps, service implementations) - •Use
publiconly for genuine external APIs - •Concrete classes implementing public interfaces should be
internal
Data Modeling
- •Records for data models
- •Favor immutability where reasonable
- •Use immutable collections:
IReadOnlyCollection,IReadOnlyDictionary
JSON Serialization
- •System.Text.Json: Use
JsonSerializerOptionsfor convention/style settings applied uniformly - •Reserve attributes (
[JsonPropertyName], etc.) for special cases only - •Check for existing options configuration before creating new instances
UsedImplicitly Attribute
Mark runtime-used members (deserialization, reflection, DI):
- •
[UsedImplicitly]- type instantiated implicitly (DI, empty marker records) - •
[UsedImplicitly(ImplicitUseKindFlags.Assign)]- properties set via deserialization - •
[UsedImplicitly(..., ImplicitUseTargetFlags.WithMembers)]- applies to type AND all members - •Common for DTOs:
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)]
Warning Suppression
- •NEVER use
#pragma warning disable - •Use
[SuppressMessage]withJustificationon class/method level - •Prefer class-level when multiple members need same suppression
LINQ
- •LINQ method chaining over loops
- •LINQ method syntax only; NEVER use query syntax (from/where/select keywords)
Method Calls
- •Named arguments for boolean literals:
new Options(SendInfo: false, SendEmpty: true) - •Named arguments for consecutive same-type parameters to clarify intent
Async
- •
ValueTaskfor hot paths - •
CancellationTokeneverywhere (usectfor variable name)
Interfaces
- •Avoid interface pollution: not every service class must have an interface
- •Add interfaces when justified (testability, more than one implementation)
Local Functions
- •Local functions go after
return/continuestatements - •Add explicit
return;orcontinue;if needed to separate main logic from local function defs
Design Principles
Quality Gates
- •Zero warnings/analysis issues - treat warnings as errors
- •All code must pass static analysis before commit
Abstraction
- •Prefer polymorphism over enums when modeling behavior or extensibility
- •Propose enum vs polymorphism tradeoffs for discussion rather than defaulting to enums
- •Every abstraction must justify its existence with concrete current needs
Dependency Injection
- •MUST use DI for all dependencies; NEVER manually
newservice objects in production code - •Concrete implementations get injected; tests can substitute
- •Search existing registrations before adding new ones
Comment Guidelines
Comments must earn their place by reducing cognitive load. When to comment:
- •LINQ chains (3+ operations): Brief comment stating transformation goal
- •Conditional blocks with non-obvious purpose: One-line comment (e.g.,
// Explicit: user specified) - •Private methods: Block comment if name + parameters don't make purpose self-evident
- •Early returns/continues: Include reason if not obvious from context
- •Complex algorithms: Comment explaining approach at top, not line-by-line
- •General: Any code where a reader would pause and wonder "why?" or "what's happening here?"
NEVER:
- •XML doc comments (unless public API library)
- •Commented-out code
- •Restating what code literally does
Tooling
Formatting
- •CSharpier is the ONLY formatting tool
- •NEVER use
dotnet formator other formatters - •Run pre-commit hooks on all changed files
dotnet CLI
- •Use dotnet CLI for: adding/removing packages, adding projects to solution
- •Central package management via
Directory.Packages.props- specify versions there, not in csproj - •Avoid
--no-buildor--no-restoreflags;dotnet testhandles restore + build automatically - •Minimal verbosity for build/test:
dotnet build -v m --no-incremental,dotnet test -v m - •For verbose debugging, pipe to file:
dotnet test -v d 2>&1 > /tmp/test.logthen search withrg
Project Structure
- •SLNX format preferred over traditional SLN
- •One Autofac/DI module per library to keep registration modular
- •Dotnet tools configured in
.config/dotnet-tools.json