csharp-best-practices
SKILL.md
C# Best Practices
Latest version: C# 14 (.NET 10, Nov 2025)
Core Rules
- Prefer
required initfor must-provide data; avoid empty strings as "uninitialized". - Prefer
IReadOnlyList<T>/IReadOnlyCollection<T>for parameters/returns to express read-only intent. - Avoid magic strings: centralize as
const/static readonly/enum/nameof(...). - Document non-
privateAPIs with XML docs (summary/param/returns/exception). - In XML docs, generic
crefuses{}(e.g.,<see cref="IReadOnlyList{T}"/>). - Use static guard methods:
ArgumentNullException.ThrowIfNull(),ArgumentOutOfRangeException.ThrowIfNegativeOrZero(). - Always propagate
CancellationTokenthrough async chains; useConfigureAwait(false)in libraries. - Avoid
async void; preferasync Taskorasync ValueTask. - Primary constructor parameters are captures, not fields — assign to a field when mutation or multiple access is needed.
- C# 14 span overload resolution may bind
array.Contains(x)toMemoryExtensions.Contains— callEnumerable.Contains()explicitly in Expression trees.
C# 14 New Features Summary
| Feature | Example | Notes |
|---|---|---|
| Extension members | extension(int n) { bool IsEven() => ...; } |
Properties, methods, static, operators |
field keyword |
set => field = value ?? throw ... |
No manual backing field needed |
| Null-conditional assignment | customer?.Order = new() |
?. on LHS of assignment |
| Partial constructors/events | partial MyClass(int x); |
Split across files |
User-defined += / -= |
void operator +=(int v) => ... |
Compound assignment operators |
Unbound generic nameof |
nameof(List<>) → "List" |
Works on open generic types |
C# 12/13 Key Features
| Feature | Version | Example |
|---|---|---|
| Primary constructors | 12 | class Svc(IDep dep) { } |
| Collection expressions | 12 | int[] a = [1, 2, .. other]; |
| Default lambda params | 12 | var f = (int x, int y = 1) => x + y; |
ref readonly params |
12 | void M(ref readonly int x) |
params collections |
13 | void M(params ReadOnlySpan<int> v) |
System.Threading.Lock |
13 | lock (new Lock()) { } |
\e escape |
13 | ESC character (U+001B) |
[^n] in initializers |
13 | Buffer = { [^1] = 0 } |
| Raw string literals | 11 | $$"""{ "id": {{id}} }""" |
| List patterns | 11 | [first, .., last] |
Pattern Matching Quick Reference
// Switch expression
string label = score switch
{
>= 90 => "A",
>= 80 => "B",
_ => "F"
};
// Property pattern
if (order is { Total: > 1000, IsPriority: true })
Console.WriteLine("VIP Rush");
// List pattern (C# 11)
if (items is [var first, .., var last])
Console.WriteLine($"{first}..{last}");
// Type + logical pattern
if (value is int x and > 0)
Console.WriteLine($"Positive: {x}");
IAsyncEnumerable Quick Reference
// Produce
async IAsyncEnumerable<int> GenerateAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
for (var i = 0; i < 100; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(10, ct);
yield return i;
}
}
// Consume
await foreach (var item in GenerateAsync(ct))
Console.WriteLine(item);
Cheat Sheet
| Topic | Practice | Since |
|---|---|---|
| Required data | required init |
C# 11 |
| Read-only APIs | IReadOnlyList<T> |
C# 1.0 |
| Avoid magic strings | const / nameof |
C# 1.0 |
| XML docs | {} in generic cref |
C# 1.0 |
| Guard methods | ArgumentNullException.ThrowIfNull() |
.NET 6 |
| Collections | [1, 2, .. spread] |
C# 12 |
| Primary constructors | class Svc(IDep dep) |
C# 12 |
| Templates | $$"""raw string""" |
C# 11 |
| Pattern matching | switch, property, list, relational |
C# 8-11 |
| Async streaming | IAsyncEnumerable<T> + await foreach |
C# 8 |
params span |
params ReadOnlySpan<T> |
C# 13 |
| Typed lock | System.Threading.Lock |
C# 13 |
| Extension members | extension(T) { ... } |
C# 14 |
field keyword |
set => field = ... |
C# 14 |
Null-conditional = |
obj?.Prop = val |
C# 14 |
record struct |
readonly record struct P(int X) |
C# 10 |
record vs record struct |
class=heap, struct=stack | C# 10 |
Additional Resources
Reference Files
For detailed rules, pitfalls, and comparisons:
references/general-rules.md— Guard methods, async best practices, primary constructor pitfalls,recordvsrecord struct, span overload resolutionBEST-PRACTICES.md— Rationale and design decisions behind each rule
Example Files
Complete, runnable .cs examples in examples/:
examples/core-example.cs— High-density example: required init, IReadOnlyList, XML docs, async/await, CancellationToken, list patterns, Parallel.ForEachAsync, record, record structexamples/csharp14-features.cs— Extension members, partial constructors/events, compound assignment, null-conditional assignment, field keyword, unbound generic nameofexamples/feature-highlights.cs— C# 12/13 features: primary constructors, collection expressions, default lambda params, params collections, Lock, raw string literals, IAsyncEnumerableexamples/pattern-matching.cs— Switch expression, property/relational/list/tuple/type patterns, pattern in if/is
Weekly Installs
2
Repository
aa89227/skillsFirst Seen
Mar 21, 2026
Security Audits
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
warp2