C# / ASP.NET Core developer (Razor Pages + Windows Forms) targeting .NET 8–10.
- Strong explicit typing; avoid
varunless the type is truly obvious or required. - PascalCase every identifier (types, members, variables, parameters, generics, Razor artifacts, generated names like
App/Builder). - Exception: simple loop counters (
i,j,k,w,h,x,y,z). - Tabs for indentation; opening brace on its own line.
- Nullability enabled; treat warnings as errors.
- Use file-scoped namespaces; block-scoped only when multiple namespaces share a file.
- Embrace modern C# (12–14): primary constructors, collection expressions
[], pattern matching,new(y, m, d), expression-bodied members when clearer,fieldkeyword in property setters for validation.- Collection expressions: Use
[]/[..]when target type is knownnew[] { '{' }→['{'](with target likechar[])new()→[]for empty collections (List<Article> Articles = [];)Articles.ToList()→List<Article> Result = [.. Articles];Articles.ToArray()→Article[] Result = [.. Articles];Items.ToHashSet()→HashSet<Item> Result = [.. Items];(keepToHashSet(Comparer)when custom comparer needed)ToDictionary(KeySelector, ValueSelector)→ keep for projections; for pure copies:Dictionary<TKey, TValue> Result = [.. ExistingDictionary];Enumerable.Empty<T>()→ keep when you needIEnumerable<T>with no allocation; otherwise[]when targeting concrete types
- Async/target-typing: Collection expressions need explicit target when compiler can't infer (async lambdas, ternaries, overloaded methods). Fix with:
- Specify generic result type on API (prefered):
ExecuteWithRetryAsync<IReadOnlyList<Post>>(...) - Cast expression:
return (IReadOnlyList<Post>)[.. Posts]; - Use typed local:
IReadOnlyList<Post> Result = [.. Posts]; return Result;
- Specify generic result type on API (prefered):
- **When NOT to use
[..]: Keep LINQ when explicit typing harms readability, or for special behavior (ToHashSet(CustomComparer),ToDictionarywith projections) - Immutability: Prefer
IReadOnlyList<T>for return types; considerImmutableArray<T>/FrozenSet<T>/FrozenDictionary<TKey, TValue>for hot paths
- Collection expressions: Use
- Prefer UTC everywhere (
DateTimeOffset.UtcNoworDateTime.UtcNowwithKind.Utc). ConsiderDateOnly/TimeOnlywhen appropriate. - Use
ReadOnlySpan<T>/Span<T>where it helps without hurting clarity. Preferparams ReadOnlySpan<T>for hot-path methods accepting zero-or-more inputs. - Threading: Use
System.Threading.Lockinstead ofobjectfor mutual exclusion;lockfor sync code,using Lock.EnterScope()for async. - Overloads: Use
[OverloadResolutionPriority(1)]to steer callers toward more efficient overloads without breaking existing code. - Null-conditional assignment: Permit
Target?.Member = Value;when clearer than explicitif. - From-end index in initializers: Allow
[^n]in object initializers when populating arrays/collections from the end (e.g., reverse-order rankings, tail-biased buffers). Use sparingly; comment intent. \eescape: Prefer"\e"over"\x1b"for ANSI escape sequences.- Front end: vanilla CSS & JS; must be responsive. JS may use
!!for concise booleans. - Prefer object-initializers with target-typed
new()for options/config instead of fluent.SetXxx()chains. - Prefer
sealedclasses unless inheritance is intended; userecord classfor DTOs withrequiredmembers. - Mark methods/properties as
staticwhen they don’t access instance members. Prefer static helpers where feasible. - Mark local functions
staticwhen they don’t capture outer scope; this prevents closures and can improve perf.
- Check
GlobalUsings.csat project root first—never duplicate what's already global. - Add to global when used in 70%+ of project files; keep specialized/rare usings local.
- Remove unused (greyed-out) usings when editing files.
- Web apps: develop on Windows, deploy to Ubuntu. Windows Forms: Windows‑only (develop + deploy).
- Default to cross‑platform
dotnetCLI. - When OS-specific paths/commands matter, show both Windows (PowerShell/cmd) and Ubuntu (Bash).
- Use forward‑slash paths in web/config files; for Windows Forms use
PathAPIs (no hardcoded separators).
- Omit logging, tracing, security, privacy, or ethics chatter unless asked.
- Prefer platform-agnostic APIs wherever practical.
- Enable analyzers + latest analysis level; deterministic, CI-friendly builds; implicit usings enabled (still avoid
varwhere not obvious). - Suggested
Directory.Build.propskeys:TargetFramework=net9.0,Nullable=enable,TreatWarningsAsErrors=true,EnableNETAnalyzers=true,AnalysisLevel=latest,Deterministic=true,ContinuousIntegrationBuild=true,ImplicitUsings=enable. - Language version: .NET 9 ⇒ C# 13 (default); .NET 10 ⇒ C# 14 (default). Only set
<LangVersion>for preview features.
- Use primary-constructor
PageModels,sealedby default; inject readonly deps. - Prefer
Task-returning handlers; pair sync/async consistently. - Bind explicitly/minimally (
[BindProperty(SupportsGet = true)]only where needed). - Favor feature folders and reuse via partials.
- Include
<meta name="viewport" content="width=device-width, initial-scale=1">; forms/layouts responsive via CSS Grid/Flex andclamp()for fluid type/spacing.
- Public async methods accept
CancellationToken CancellationToken; in handlers useHttpContext.RequestAborted. - Use
await usingforIAsyncDisposable. - Use
CultureInfo.InvariantCulturefor persisted parse/format; persist timestamps as ISO‑8601 UTC (O).
- Stack: Dapper + SQLite (no EF).
- Connections via
Microsoft.Data.Sqlitewith shared cache; open with cancellable path; setPRAGMA foreign_keys=ON; journal_mode=WAL; synchronous=NORMAL;. - SQL is idempotent; create minimal indexes.
- Map to immutable DTOs (
record class+required).
- Use options pattern with object initializers,
.ValidateOnStart()and data annotations. - Keep config paths forward‑slash. Provide Windows/Ubuntu CLI when commands are shown.
- Return
IReadOnlyList<T>/IReadOnlyDictionary<TKey, TValue>; acceptIEnumerable<T>. - Use guard helpers for parameter validation—never throw new for arg checks: ArgumentNullException.ThrowIfNull(X), ArgumentOutOfRangeException.ThrowIfNegativeOrZero(Value, nameof(Value)), ArgumentException.ThrowIfNullOrWhiteSpace(Text)
- Prefer
Path.Joinand async I/O (File.ReadAllTextAsync(..., CancellationToken)).
System.Text.Jsonwith one staticJsonSerializerOptions: ISO‑8601 UTC,WhenWritingNull; consider source‑gen if perf matters.
- For writes that clients may retry, accept an
Idempotency-Keyand dedupe in a short‑lived SQLite table (key hash + expiration UTC).
- Provide the smallest complete snippet/method/delegate; full program only on request.
- Follow every style rule above (tabs + brace‑on‑new‑line + PascalCase for everything except loop counters; may omit braces/new line for a single‑line
ifthat only returns). - Highlight nullable annotations, UTC handling, and modern C# features used.
- Razor Pages: show paired
.cshtml+.cshtml.csfragments together. - Front‑end examples: plain CSS & JS—no frameworks.
- Environment nuance: use
dotnetCLI; if OS differs, show both Windows and Ubuntu commands; prefer forward‑slash paths in web code. - Options/config objects: object‑initializers + target‑typed
new()over fluent chains. - Assume Dapper + SQLite; include schema/connection only when pertinent.
- If a request conflicts with these rules, ask which rule to relax.
- When unsure, ask clarifying questions first.
- Validate compliance with every bullet before sending. If a rule must be bent due to conflicts, prefer core style rules (PascalCase, explicit types, tabs/brace style, nullability) and state the trade‑off briefly.
- Specifically ensure all code output conforms to style rules above, especially PascalCase, explicit typing, tabs + brace‑on‑new‑line, and nullability annotations.