C# Code Quality

A handy checklist to help you review C# code for quality, security and performance so every pull request is ready for production.

What We Cover in the C# Analysis​

Here’s what this checklist helps you review before approving a pull request.

Code & Design Quality – consistent .NET style, clear structure, modern C# patterns and proper dependency management
Security & Performance – validated inputs, safe deserialization, secrets handling, efficient queries, caching and scalable runtime
Reliability & Observability – robust error handling, structured logging/tracing, metrics, health checks and safe CI/CD rollouts
Data & Integration – versioned APIs, safe migrations and transactions, proper indexing and clean configuration/secrets management

C# Code Review Checklist

Export this Checklist as Markdown

Use these points as a starting guide to automate code reviews with your agent.

Names follow .NET conventions (PascalCase for types/methods, camelCase for locals/parameters, ALL_CAPS for const).

Files and classes are cohesive; no “God” classes; methods are short and intention-revealing.

No dead/commented code; TODOs tracked in issues, not left in source.

Consistent formatting via EditorConfig/formatter (dotnet-format/Rider); no style bikeshedding in PR.

using directives are trimmed, sorted, and file-scoped; no wildcard usings.

Early returns over deep nesting; guard clauses for validation.

Comments/docstrings explain why, not what; public APIs have XML docs where non-obvious.

LINQ pipelines remain readable; extract steps/locals when complex; avoid hidden multiple enumerations.

String handling uses interpolation or StringBuilder in loops; culture/format specified where important.

Nullable Reference Types (NRT) enabled and warnings treated seriously; nullability annotations correct.

Immutability favored: readonly fields/structs, record for value types/DTOs; defensive copies for mutable inputs.

Visibility is minimal; avoid public setters where not required; use init-only setters.

Prefer composition over inheritance; abstract classes only for shared behavior contracts.

Avoid static/global state; inject dependencies; no service locator.

Pattern matching/enhanced switch used where it clarifies logic; avoid deep if/else.

Don’t throw from property getters; keep constructors free of heavy I/O/logic—use factories/builders.

IDisposable implemented correctly; use using/await using for disposable/async-disposable resources.

Public APIs fully typed (no object/dynamic unless necessary); generic constraints declared precisely.

Interfaces model behavior not data; segregation respected (ISP).

IReadOnlyCollection<>/IEnumerable<> returned when mutation not intended.

Use Span<T>/Memory<T>/ReadOnlySpan<T> for hot paths on buffers (when safe).

Contracts documented (pre/postconditions) and validated at boundaries; guard clauses throw specific exceptions.

Avoid null for collections; return empty collections.

All inputs validated and normalized (FluentValidation/DataAnnotations); server-side enforced.

Parameterized queries only (Dapper/EF Core); no string-concat SQL; protect against ORM injection.

XSS: Razor auto-encoding respected; no raw HTML without sanitization.

CSRF: antiforgery tokens on state-changing endpoints (if cookie-based auth).

Authentication via ASP.NET Core auth handlers; authorization policies/voters applied at every endpoint.

No sensitive data in logs; PII redaction; GDPR/LGPD awareness.

Secrets from secret store (Azure Key Vault, AWS Secrets Manager); never in repo or images; rotation plan exists.

Security headers set (HSTS, X-Content-Type-Options, X-Frame-Options/frame-ancestors, CSP where possible).

SSRF mitigations: outbound allow-list; block link-local/metadata IPs; DNS rebinding defenses.

Deserialization safe (System.Text.Json with known types); no BinaryFormatter; no insecure YAML/XML parsers (XXE).

Rate limiting/anti-abuse on auth and expensive endpoints; lockouts for brute force.

Hot paths avoid allocations; use pooling (ArrayPool, ObjectPool) when appropriate; avoid boxing.

Asynchronous I/O (async/await) for network/disk; do not block threads (.Result, .Wait()).

Avoid sync-over-async; configure awaits with ConfigureAwait(false) in libraries.

LINQ vs loops chosen based on profiling; avoid multiple enumerations; ToList() only when needed.

Caching strategy defined (MemoryCache/Distributed cache); keys/TTLs/invalidation documented.

EF Core: no N+1; projection selects; AsNoTracking for read-only; compiled queries when hot.

HTTP client reuse via IHttpClientFactory; timeouts set; connection pooling validated.

JSON serialization uses System.Text.Json with source-gen for hot payloads when beneficial.

GC mode/server GC settings appropriate; thread pool starvation monitored.

Benchmarks (BenchmarkDotNet) for disputed hotspots; optimize after measuring.

Exceptions are specific; no swallow of Exception; catch-wrap-rethrow with context when useful.

Domain vs technical errors distinguished; problem details returned to clients (RFC7807) without leaking internals.

All external calls have timeouts; retries are bounded with jitter and idempotent-only; circuit breaker/bulkhead where needed.

Logging is structured (serilog/MEL) with correlation IDs; no sensitive values.

Metrics (RED/USE) exported (Prometheus/OpenTelemetry); SLIs/SLOs defined.

Tracing with OpenTelemetry spans for inbound/outbound; context propagated.

Health/ready/live endpoints implemented; shutdown is graceful (drain requests, close brokers).

Unit tests deterministic, fast (xUnit/NUnit/MSTest); naming reflects behavior.

Integration tests cover DB/cache/HTTP/brokers (Testcontainers); isolated and parallelizable.

E2E or contract tests for public APIs (OpenAPI validation, Pact for consumer/provider).

Property-based tests (FsCheck) for pure logic/parsers.

Regression tests accompany bug fixes; fail without the fix.

Coverage monitored on critical paths/error handling; quality > raw %.

Static analysis: Roslyn analyzers/StyleCop/FxCop rules; nullable warnings treated as errors where possible.

Mutation testing (Stryker.NET) on critical modules when justified.

Security scans (DevSkim/GitHub CodeQL) in CI; failing gates block merge.

Layered/hexagonal boundaries enforced; no infrastructure bleeding into domain.

Dependency direction respected (core has no refs to UI/infra); abstractions stable, implementations swappable.

Modules small and cohesive; cyclical references avoided.

Feature flags for risky behavior; toggles centralized and typed.

ADRs/design notes for non-trivial changes; rationale captured.

Public contracts versioned; backward compatibility considered.

Patterns reduce complexity/duplication, not add ceremony.

Factories/Builders for complex object creation; no telescoping constructors.

Strategy/Policy to replace if/else pyramids; Mediator for decoupled request handling (with care).

CQRS only where read/write models diverge meaningfully; events have clear ownership and delivery guarantees.

Anti-patterns avoided: service locator, anemic domain (unless choosing transactional script explicitly), God objects.

Project uses SDK-style csproj; Directory.Packages.props or central package mgmt where appropriate.

Deterministic builds; packages.lock.json (NuGet lock) enabled for apps.

Semantic Versioning respected for libraries; package metadata complete.

SBOM generated (e.g., SPDX/CycloneDX); vulnerability scanning configured.

Strong-naming/signing applied where required; assembly attributes correct.

Third-party licenses reviewed; runtime RID assets well defined.

API surface consistent; versioning via URL/header; breaking changes gated.

OpenAPI accurate and generated; request/response models validated.

Consistent error model (problem+json); validation errors structured.

Minimal APIs use typed endpoints/filters properly; Controllers keep thin; business logic in services.

gRPC: deadlines/timeouts set; message sizes and compression configured; auth enforced.

Idempotency keys for unsafe POST; webhook signatures (HMAC + timestamp) verified; retries deduped.

async/await used correctly; no blocking on async; cancellation tokens plumbed end-to-end.

Task lifetimes managed; unobserved exceptions handled; Task.Run only for CPU-bound offload.

Concurrency control with SemaphoreSlim/Channel<T>/Dataflow where suitable; backpressure enforced.

Thread-unsafe members protected; locks minimal and fine-grained; deadlock risks reviewed.

Parallel LINQ/Parallel.ForEachAsync used intentionally with sizing limits.

EF Core: explicit transactions where needed; correct isolation; SaveChanges bounded in scope.

Migrations atomic and forward-only (with rollback plan if necessary); data backfills planned.

Indexes match query patterns; no full scans on hot paths; query plans checked for regressions.

Bulk operations use proper APIs; batching/pagination for large sets.

UTC in storage; DateTimeOffset/Instant where needed; precision/scale defined for money/decimals.

Middleware order correct (exception handling, routing, auth, endpoints).

Model binding safe; validation attributes/FluentValidation applied; custom binders minimal and tested.

Filters used for cross-cutting concerns (authz, caching, transactions) not scattered in controllers.

CORS minimal; compression/caching configured; response buffering considered.

Typed options (IOptions<T>) with validation; fail fast on invalid config.

appsettings.*.json per environment; no environment-specific branches in code.

Secrets from vault/SM; not in repo or images; rotation tested; local dev uses user-secrets.

Feature flags/config centralized; dynamic reload where safe.

CI runs lint/analyzers/tests/security scans; gates block merges; artifacts reproducible.

dotnet publish with correct RID/-c Release; single-file/AOT/trimming considered (and annotations added) where suitable.

Containers minimal, non-root, read-only FS; proper health checks; small attack surface.

Kubernetes: liveness/readiness/startup probes; resource limits/requests; pod disruption budgets.

Rollouts blue/green or canary; fast rollback path; DB compatibility checks in pipeline.

Startup/shutdown hooks drain queues and close pools.

Structured logs (JSON) with correlation/causation IDs; scope enrichers used.

Sampling configured for noisy components; PII redaction in sinks.

Metrics exported (Prometheus/OpenTelemetry) with RED/USE coverage.

Tracing spans for inbound/outbound and DB/cache; baggage/trace context propagated across services.

PII cataloged; access controls audited; least privilege enforced.

Data retention and erasure implemented; subject requests supported; audit trails immutable.

Encryption in transit/at rest; key mgmt documented; regional residency respected.

Third-party processors reviewed; DPAs in place.

CLI/console apps use System.CommandLine/Spectre; helpful --help; exit codes meaningful.

Scripts deterministic and idempotent; cross-platform (PowerShell + Bash); pinned tool versions.

Service README with purpose, dependencies, run/debug instructions, and operability notes.

XML docs for public packages; examples compile; API docs generated (Swashbuckle + annotations).

ADRs for significant choices; upgrade notes/changelog maintained.

Runbooks/on-call playbooks updated for new behaviors and alerts.

IaC (Bicep/Terraform/Pulumi) versioned; reviewable changes; drift detection.

Resiliency patterns: retries, timeouts, circuit breakers, bulkheads; chaos testing where feasible.

Cost budgets/alerts; right-sized SKUs; caching/queueing to reduce load.

Backups tested; RPO/RTO documented; disaster recovery drills scheduled.

Turn this checklist into automated reviews

Let Kody automatically check quality, security and performance in every pull request—cloud or self-hosted, in under 2 minutes.