Skip to content
Skill · GitHub Copilot.github/copilot-instructions.mdCoding

Copilot Instructions for .NET / C# Projects

C# conventions for Copilot: nullable reference types treated as errors, Async suffix on every awaitable method, CancellationToken plumbing, and xUnit patterns that scale.

Use when: Use in a .NET 8+ solution when you want Copilot suggestions to respect nullable annotations, async conventions, and your xUnit test layout instead of producing 2015-era C#.

@libraryNewcomer
0 upvotes0 forks0 comments

The skill file

GitHub Copilot · .github/copilot-instructions.md
.github/copilot-instructions.md3083 chars
# .NET Project Instructions ## Nullable reference types - `<Nullable>enable</Nullable>` and `<WarningsAsErrors>nullable</WarningsAsErrors>` are set solution-wide. Never suggest code that introduces a nullable warning. - The null-forgiving operator `!` is allowed only in test arrange blocks and DI wire-up; in production code, handle the null or change the signature. - Public method parameters that must not be null get a guard: `ArgumentNullException.ThrowIfNull(arg);` as the first line. Do not write `if (arg == null) throw ...` long-form. - Prefer returning empty collections over null. A method returning `List<T>?` is almost always wrong — return `IReadOnlyList<T>` and `[]`. - Model "value may be missing" with a nullable annotation, not magic defaults like `string.Empty` or `-1`. ## Async conventions - Every method returning `Task`/`Task<T>`/`ValueTask` is named with the `Async` suffix, including private methods. No exceptions for controllers. - Every public async method accepts a `CancellationToken cancellationToken = default` as its last parameter and passes it to every awaited call that accepts one. - Never use `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()` — if you are in a sync context that needs async work, make the caller async instead. - `async void` is allowed only for event handlers; everywhere else return `Task`. - Use `await foreach` with `IAsyncEnumerable<T>` for streamed reads; do not buffer a whole table into a list just to iterate it. ## Language and style - File-scoped namespaces, `var` when the type is apparent, target-typed `new()` where the type appears on the left. - Records for DTOs and messages; classes for entities and services. DTO records are `sealed`. - Pattern matching (`is`, `switch` expressions) over chains of `if`/`else if` on type or enum. - LINQ method syntax only; no query syntax. Avoid multiple enumeration — materialize with `.ToList()` once if you iterate twice. - Exceptions: throw the most specific BCL type that fits; custom exceptions only when callers need to catch them specifically. ## xUnit patterns - Test classes mirror the source layout: `OrderService` in `src/Billing/` is tested by `OrderServiceTests` in `tests/Billing.Tests/`. - Test names follow `MethodName_Scenario_ExpectedOutcome`, e.g. `RefundAsync_AlreadyRefunded_ThrowsInvalidOperation`. - One logical assert per test; use `Theory` with `InlineData`/`MemberData` instead of copy-pasted `Fact`s that differ by one value. - Shared expensive setup goes in an `IClassFixture<T>`; never static mutable state between tests. - Mock only external boundaries (clock, HTTP, message bus) with NSubstitute; never mock the class under test's own collaborators if a real instance is cheap. - Async tests return `Task` and await the act step — never `async void` tests. ## Workflow - Run `dotnet build -warnaserror` and `dotnet test` before suggesting a commit. - Never edit files under `obj/`, `bin/`, or `*.Designer.cs`. - New projects target the same TFM as the solution; do not add a package reference when the BCL already covers the need.

Install

drop into your repo

Save this to your project or home directory so GitHub Copilot can load it.

path./.github/copilot-instructions.md
Discussion

What people are saying

Forks

0 forks of this skill
Loading forks…