C# style rules
Standing rules for C# an assistant writes, covering nullable reference types, async all the way with cancellation tokens, records and pattern matching, dependency injection and xUnit tests.
When you write or change C# code in this project:
Tooling and version
- Use the target framework and
LangVersionthe project files declare, and only features they support. Do not change them on your own. - Follow the repository's
.editorconfigand analyzers, and keep the build free of new warnings. Use file-scoped namespaces and the project's existing conventions forusingdirectives. - Add NuGet packages only when the base class library cannot do the job in a few lines, through the project's central package management if it has it.
Nullable reference types
- Code assumes
<Nullable>enable</Nullable>. Annotate every reference that can be null with?and handle it; never silence warnings with the null-forgiving operator unless a comment explains why the value cannot be null. - Validate public arguments with
ArgumentNullException.ThrowIfNull(arg)and the relatedThrowIfhelpers. - Return empty collections, not
null. Use theTrypattern (bool TryGet(..., out T value)) or a nullable return when absence is normal.
Async
- Async all the way: never block on tasks with
.Result,.Wait()orGetAwaiter().GetResult(). ReturnTaskorTask<T>; useasync voidonly for event handlers. - Every async method that does I/O takes a
CancellationToken cancellationTokenas its last parameter (optional with= defaulton public APIs, as the framework does) and passes it to every call that accepts one; analyzer CA2016 flags the calls where it is dropped. - Name async methods with the
Asyncsuffix. UseConfigureAwait(false)in library code; it is not needed in ASP.NET Core application code. - Use
ValueTaskonly where a measurement shows allocation matters. UseIAsyncEnumerable<T>for streaming results, andawait usingforIAsyncDisposable.
Types and language features
- Use records (or
record struct) for immutable data,initaccessors andrequiredmembers for object construction, and keep mutable state private. - Prefer switch expressions and pattern matching over
if/elsechains on types or values, with a discard arm that throws for unexpected cases. - Use
DateTimeOffsetfor timestamps and injectTimeProvider(.NET 8 and later; otherwise the project's clock abstraction) where code needs the current time, neverDateTime.Nowin logic. Usedecimalfor money. - Always pass a
StringComparisonto string comparisons andIndexOf/StartsWithcalls; useStringComparer.OrdinalIgnoreCasefor case-insensitive keys.
Dependency injection and configuration
- Use constructor injection (primary constructors if the project uses them). No service locator calls to
IServiceProviderinside business code. - Register lifetimes correctly: never inject a scoped service (such as a
DbContext) into a singleton. Bind configuration to options classes withIOptions<T>and validate them at startup. - Create HTTP clients through
IHttpClientFactoryor typed clients, nevernew HttpClient()per call.
Errors and resources
- Throw specific exceptions with useful messages. Rethrow with
throw;to keep the stack trace, neverthrow ex;. Never catchExceptionto ignore it; catch broadly only at a boundary that logs and translates. - Dispose
IDisposableresources withusingdeclarations. Do not use exceptions for normal control flow.
Data access and LINQ
- Keep LINQ readable; avoid enumerating the same
IEnumerabletwice (materialise once withToList()when needed). - With Entity Framework Core, use async query methods with the cancellation token,
AsNoTracking()for read-only queries, and projections orIncludeto avoid N+1 queries.
Logging
- Use
ILogger<T>with message templates and named placeholders:logger.LogInformation("Order {OrderId} shipped", orderId). Never string interpolation in log calls, and never log secrets or personal data. Use theLoggerMessagesource generator on hot paths if the project does.
Tests (xUnit)
- Use
[Fact]for single cases and[Theory]with[InlineData]or[MemberData]for input tables. Name testsMethod_Scenario_ExpectedResultor follow the project's existing scheme. - Put setup in the constructor and cleanup in
DisposeorIAsyncLifetime; no shared static mutable state between tests. - Use the assertion library the project already uses, and
await Assert.ThrowsAsync<TException>(...)for async failures, checking the exception type and message. - Mock only at boundaries (HTTP, storage, time) with the project's mocking library; use a fake
TimeProviderfor time. NeverThread.SleeporTask.Delayto wait for work in tests.
details
- kind
- Rule: standing instructions for everything the assistant does
- domain
- Software engineering
- category
- Conventions
- made for
- Software engineer, Backend engineer
- risk
- read-only
- version
- v1.0.0 · experimental
- reviewed
- 2026-10-02
- works in
- Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Antigravity, OpenCode, Windsurf, Zed, Continue, AGENTS.md, ChatGPT, claude.ai
use in
npx @hermes-hq/hodios install csharp-style-rules --target claude-codenpx skills add hermes-hq/hodios-dist --skill csharp-style-rules -a claude-codeRules are always-on instructions, so they are not in the plugins: add the skill, or paste the text into CLAUDE.md.
pairs well with
All of ConventionsTest-writing rules
Standing rules for tests an assistant writes, covering behaviour over implementation, no sleeps, deterministic data, mocks only at boundaries and one reason to fail per test.
test-writing-rulesSecure coding rules
Makes the assistant write code that validates untrusted input, avoids injection, protects secrets and checks authorization by default. Use as always-on rules in any codebase.
secure-coding-rulesHTTP API design rules
Rules for HTTP APIs covering resource naming, status codes, problem+json errors, cursor pagination, idempotency keys and versioning. Load when designing or changing HTTP endpoints.
api-design-rulesGo style rules
Standing rules for Go an assistant writes, covering wrapped errors, context propagation, small consumer-side interfaces, table-driven tests and no goroutines without an owner.
go-style-rulesJava style rules
Standing rules for Java an assistant writes, covering modern language features, immutability, Optional and null handling, exceptions, restrained streams, records and JUnit 5 tests.
java-style-rulesPython style rules
Standing rules for Python an assistant writes, covering type hints, pathlib, logging over print, explicit exceptions, safe subprocess calls, project layout and the project's own tooling.
python-style-rules