C# Style
Hold the code to an enterprise production standard, never the level of tutorial or learning material. Write in a strict, technical style: no explanatory scaffolding, no didactic comments.
Naming and layout
- Give every identifier its full domain meaning; never use a single-character or abbreviated name.
- Suffix a method returning
TaskorTask<T>withAsync. - Follow .editorconfig rules
Language features
- Use the
varkeyword to declare variables. - Use the newest language features, patterns, types.
- Use
nameofinstead of a string literal for a member or parameter name. - Use raw string literals (
""") for multi-line or quote-heavy text, and interpolation elsewhere. - Use primary constructors to capture dependencies and simple state.
- Use
requiredmembers instead of a constructor whose only job is to force initialization. - Use range and index operators (
^,..) for slicing. - Use
is nulloris not nullfor null checks; never the empty property patternis { }as a null check. - Use separate
ifblocks forif-returnconditions; don't list all the conditions within a singleifblock. - Use pattern matching, collection expressions, switch expressions.
- Use declaration pattern to check the run-time type of an expression and, if a match succeeds, assign an expression result to a declared variable.
- Use type pattern to check the run-time type of an expression.
- Use constant pattern to test that an expression result equals a specified constant.
- Use relational patterns to compare an expression result with a specified constant.
- Use logical patterns to test that an expression matches a logical combination of patterns.
- Use property pattern to test that an expression's properties or fields match nested patterns.
- Use positional pattern to deconstruct an expression result and test if the resulting values match nested patterns.
- Use var pattern to match any expression and assign its result to a declared variable.
- Use discard pattern to match any expression.
- Use list patterns to test that a sequence of elements matches corresponding nested patterns.
- Use source generators from System and external libraries;
ObservableProperty,LoggerMessage,JsonSerializerContextand others. - Use the
System.Memoryfeatures and types, if this doesn't reduce code readability. - Use
Polyfillwhen the project targets multiple frameworks; it brings new types and methods to legacy targets without copying .NET sources.
Nullability
- Use nullable types; keep public and internal contracts null-safe.
- Use
= null!suppression only where the value is never null.
Annotations
- Express contracts with annotations from the JetBrains and
System.Diagnostics.CodeAnalysissets — both are large; reach for whichever fits, not a fixed few. - Use
[Pure]if the method doesn't make any observable state changes. - Use
[NotNullWhen]onTry-style methods with anoutnullable result. - Use
[PublicAPI]to mark a publicly available API or DTO that must not be removed. The annotation keeps the symbol out of the unused-symbol report. - Use
[UsedImplicitly]to mark a symbol as used implicitly. - Use
[MustUseReturnValue],[MemberNotNull],[DoesNotReturnIf],[StringSyntax]and others if applicable.
Asynchronous code
- Use
Task/Task<T>; reserveasync voidfor a framework-required event handler. - Flow
CancellationTokenthrough I/O, broker, storage, and long-running work. - Never block an async flow with
.Result,.Wait(), or synchronous sleeps.
Data contracts
- Use
recordfor DTOs, message contracts, and configuration-style data. - Use
initproperties if applicable.
Error handling
- Guard a public method's arguments at entry; throw
ArgumentNullException,ArgumentException, orArgumentOutOfRangeException. - Throw the most specific exception type; never throw
Exception,SystemException, orApplicationExceptiondirectly. - Rethrow with
throw;; neverthrow exception;.
Extensions methods and properties
- An extension method is declared inside an
extensionblock, never with athisparameter. - A registration extension is named for its net effect on the container:
Add*when something resolves after the call that did not before,Configure*when the call only supplies settings. Split a registration by phase, never by verb. - The file suffix of an extension class follows the host phase, never the verb of the method inside.
*Registration.csholds everything that runs beforeBuild(), whether the method readsAdd*orConfigure*.*Endpoints.csholds everything that runs after it, theMap*calls onWebApplicationandIEndpointRouteBuilder. A class that carries both phases is split into two files. *Configuration.csnames a type that configures something — anIConfigureOptions<T>or an equivalent configurator — and never an extension class.*Extensions.csnames ordinary extension methods over a domain or framework type, and an Aspire resource decorator returningIResourceBuilder<T>keeps theWith*verb.
Performance
- Do not use deep optimization if it affects code readability.
- Use
Spanif it avoids allocations without significant code changes. - Use
structfor internal value types on a hot path; keep them inside the owning type and don't expose them across a public boundary. A value type allocates nothing. - Dispose owned streams and pooled resources.
- Unsubscribe from events depending on the object lifetime.
- Use source-generated types in place of their reflection-based equivalents. A generated type carries no extra allocation and no reflection overhead.
Comments
- Comment the intent, constraint, or invariant the code cannot show; add none when the code already states it.
- Judge every comment as final standalone text: the reader has only the code as it stands, with no previous version, diff, or request to compare against; drop any comment that merely narrates the change.
- State facts in the present indicative; never argue why the code is as it is.
- Cut every purpose, result, cause, or comparison clause (
so,that makes,which makes,because,rather than); if the clause states a fact the reader needs, make it its own sentence.
Review
- Identifiers carry full domain meaning; async methods end in
Async. - Modern language features are used:
var, pattern matching, collection and switch expressions, and source generators over hand-written equivalents. - Nullability is explicit and annotations match real contracts.
- Async paths flow
CancellationTokenand never block. - Shared data contracts are immutable
recordtypes; a DTO carries[PublicAPI]. - Exceptions are specific types, arguments are guarded at entry, and rethrows use
throw;. - Hot paths avoid needless allocations (
struct,Span); owned disposables and event subscriptions are released on the owner's lifetime. - Comments state facts about the code as it stands; none narrates the edit or argues why.