modern-csharp
Skill5 starsChanged 6 days agoMITView on GitHub
C# 12–14 gotchas on .NET 10 — primary constructors, collection expressions, records, pattern matching, nullable reference types, the `field` keyword, extension members, null-conditional assignment, and async/cancellation pitfalls.
4 other projects use a version of this.
Adopted by
Most recently taken up by
- codewithmukesh/dotnet-claude-kittheir own version, first seen Nov 8, 2026
- snuffish/dotfilestheir own version, first seen Nov 1, 2026
- Resgrid/Coretheir own version, first seen Oct 8, 2026
- MovGP0/Skillstheir own version, first seen Jun 30, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- codewithmukesh/dotnet-claude-kit★ 808same name
- Resgrid/Core★ 238same name
- MovGP0/Skills★ 1same name
- snuffish/dotfiles★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- aspnet-api-patternsSkill
- authenticationSkill
- cachingSkill
- clean-architectureSkill
- convention-learnerSkill
- dddSkill
- dotnet-project-initSkill
- ef-core-patternsSkill
- error-handlingSkill
- loggingSkill
- opentelemetrySkill
- resilienceSkill
- testing-dotnetSkill
- vertical-sliceSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 25
Don't downcast it to List<T> or T[].
- Very specific: line 53
Prefer is { } x / is not null over != null — user-defined == operators can change the result of the latter.
- Very specific: line 118
Never .Result, .Wait() or .GetAwaiter().GetResult() on a call path that could be async — it blocks a thread-pool thread and deadlocks under …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Modern C# (12–14)
.NET 10 ships C# 14 as the default language version — no <LangVersion> needed. The syntax of these
features is assumed known; this file carries the traps.
Primary Constructors (C# 12)
- Parameters are captured as mutable hidden fields, not
readonly. Assigning to one inside a method mutates class state. When that matters, copy intoprivate readonly T _x = x;and use only the field. - Using a parameter both to initialise a field and directly in a member body captures it twice (warning CS9124) — two copies that can diverge. Pick one.
- On records, primary-constructor parameters become public properties; on classes and structs they do not.
Collection Expressions (C# 12)
- The concrete type behind
IEnumerable<T> x = [..]/IReadOnlyList<T> x = [..]is compiler-chosen. Don't downcast it toList<T>orT[]. [..a, ..b]allocates a new collection every time. On a hot path that is a real allocation, not a view.
Records
- Equality is member-wise, but collection members compare by reference: two records holding equal
List<T>contents are not equal. Records used as EF keys, cache keys or dedupe keys need scalar members or a customEquals. withis a shallow copy — a mutated list inside the copy is the original's list.record structis a value type; it is not guaranteed to be stack-allocated (boxed in interfaces, on the heap as a field of a class). Unlikereadonly record struct, it is mutable.requiredmembers are bypassed by a constructor marked[SetsRequiredMembers]— the compiler then trusts that constructor to set them all.
Pattern Matching
is not null or 42parses as(is not null) or 42; the compiler warns that42is redundant. Writeis not (null or 42).- A switch expression without a discard arm throws
SwitchExpressionExceptionat runtime on an unmatched value. Add_ => throw new UnreachableException()so the failure is explicit.
Nullable Reference Types
- Annotations are compile-time only. Deserialisers (System.Text.Json, EF materialisation, model
binding) can still hand you
nullin a non-nullable property — validate at the boundary. !silences the warning without changing runtime behaviour. Each one should have a reason the flow analysis can't see.- Prefer
is { } x/is not nullover!= null— user-defined==operators can change the result of the latter.
field Keyword (C# 14, GA)
public string Name
{
get;
set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value));
}- Inside any property accessor,
fieldnow binds to the synthesised backing field. An existing member namedfieldis shadowed (warning CS9258). Refer to it asthis.fieldor@field, or rename it. A local namedfieldinside an accessor is an error (CS9272).
Extension Members (C# 14)
Extension blocks add extension properties and static extension members, not just methods:
public static class EnumerableExtensions
{
extension<TSource>(IEnumerable<TSource> source) // instance extensions: receiver is named
{
public bool IsEmpty => !source.Any();
}
extension<TSource>(IEnumerable<TSource>) // static extensions: receiver type only
{
public static IEnumerable<TSource> Identity => [];
}
}- Classic
this-parameter extension methods keep working and can live in the same static class. extensionis now a contextual keyword: a type, alias or type parameter namedextensionno longer compiles. Escape it as@extension.
Null-Conditional Assignment (C# 14)
customer?.Order = GetCurrentOrder(); // GetCurrentOrder() runs only when customer is non-null
counter?.Total += 1; // compound assignment works- The right-hand side is not evaluated when the receiver is null. Side effects there are skipped too.
++/--are not allowed on a null-conditional target.
Implicit Span Conversions (C# 14)
Arrays now convert implicitly to Span<T> / ReadOnlySpan<T>, so overload resolution can pick a
span overload where C# 13 picked IEnumerable<T>:
- Some calls become ambiguous (for example xUnit
Assert.Equal(expectedArray, actualArray)with collection-expression arguments). Fix with.AsSpan()or an explicit type. - A covariant array (
object[] o = stringArray) passed to a newly chosenSpan<T>overload throwsArrayTypeMismatchExceptionat runtime.
Async & Cancellation Gotchas
- Thread the
CancellationTokenall the way down: every async call that accepts one gets it. A token that stops at the service layer means an abandoned request keeps the DB query running. - Never
.Result,.Wait()or.GetAwaiter().GetResult()on a call path that could be async — it blocks a thread-pool thread and deadlocks under a synchronisation context. ConfigureAwait(false): skip it in ASP.NET Core app code (there is no synchronisation context); use it in reusable libraries that may run under a UI or legacy ASP.NET context.- A
ValueTaskmay be awaited once. Don't await it twice, store it, or.Resultit before it completes — call.AsTask()if you need any of that. async voidonly for event handlers: an exception in it crashes the process instead of reaching the caller.- Catching
OperationCanceledExceptionwhen the request token fired is not an error — let it propagate or log it at Information level, not Error. - Linked or timeout
CancellationTokenSources areIDisposable—using var cts = .... - Returning a task without
awaitfrom inside ausing/tryblock disposes or exits before the task finishes. Await it.
See Also
skills/error-handling/SKILL.md— theResult<TValue, TError>type these features build onskills/clean-architecture/SKILL.md— layer conventions these features apply within
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

