|
| 1 | +# Modern C# Features For .NET Repositories |
| 2 | + |
| 3 | +Use this reference when modernizing C# code without breaking the repo's target framework, SDK, or language-version expectations. |
| 4 | + |
| 5 | +## First Rule: Detect The Real Language Ceiling |
| 6 | + |
| 7 | +- the default C# version follows the target framework in modern SDK-style projects |
| 8 | +- `.NET 9` maps to `C# 13` |
| 9 | +- `.NET 10` maps to `C# 14` |
| 10 | +- newer language versions than the target framework supports are not supported |
| 11 | +- do not set `<LangVersion>latest</LangVersion>` because it makes builds machine-dependent |
| 12 | +- use `<LangVersion>preview</LangVersion>` only when the repo intentionally opts into preview |
| 13 | + |
| 14 | +Useful checks: |
| 15 | + |
| 16 | +```bash |
| 17 | +rg -n "<TargetFramework|<TargetFrameworks|<LangVersion" -g '*.csproj' -g 'Directory.Build.*' . |
| 18 | +``` |
| 19 | + |
| 20 | +If the repo's current language version is unclear, the compiler can reveal it: |
| 21 | + |
| 22 | +```csharp |
| 23 | +#error version |
| 24 | +``` |
| 25 | + |
| 26 | +As of March 8, 2026: |
| 27 | + |
| 28 | +- `C# 14` is the latest stable version |
| 29 | +- `C# 15` exists in preview, but should not be a default choice for production repo guidance |
| 30 | + |
| 31 | +## Practical Adoption Rules |
| 32 | + |
| 33 | +- prefer stable features that remove ceremony, improve correctness, or improve performance without obscuring intent |
| 34 | +- do not rewrite entire codebases just to use new syntax |
| 35 | +- modernize opportunistically when touching the code anyway |
| 36 | +- coordinate feature adoption with `.editorconfig`, analyzers, and architecture rules |
| 37 | + |
| 38 | +## Version Guide |
| 39 | + |
| 40 | +### C# 8 |
| 41 | + |
| 42 | +Typical pairing: `.NET Core 3.x` |
| 43 | + |
| 44 | +Key features: |
| 45 | + |
| 46 | +- readonly members |
| 47 | +- default interface members |
| 48 | +- switch expressions |
| 49 | +- property patterns |
| 50 | +- tuple patterns |
| 51 | +- positional patterns |
| 52 | +- using declarations |
| 53 | +- static local functions |
| 54 | +- disposable `ref struct` |
| 55 | +- nullable reference types |
| 56 | +- asynchronous streams |
| 57 | +- indices and ranges |
| 58 | +- null-coalescing assignment |
| 59 | +- unmanaged constructed types |
| 60 | +- stackalloc in nested expressions |
| 61 | +- improved interpolated verbatim strings |
| 62 | + |
| 63 | +### C# 9 |
| 64 | + |
| 65 | +Typical pairing: `.NET 5` |
| 66 | + |
| 67 | +Key features: |
| 68 | + |
| 69 | +- records |
| 70 | +- init-only setters |
| 71 | +- top-level statements |
| 72 | +- relational patterns |
| 73 | +- logical patterns |
| 74 | +- native-sized integers |
| 75 | +- function pointers |
| 76 | +- module initializers |
| 77 | +- target-typed `new` |
| 78 | +- static anonymous functions |
| 79 | +- target-typed conditional expressions |
| 80 | +- covariant return types |
| 81 | +- extension `GetEnumerator` support in `foreach` |
| 82 | +- lambda discard parameters |
| 83 | +- attributes on local functions |
| 84 | + |
| 85 | +### C# 10 |
| 86 | + |
| 87 | +Typical pairing: `.NET 6` |
| 88 | + |
| 89 | +Key features: |
| 90 | + |
| 91 | +- record structs |
| 92 | +- struct initialization improvements |
| 93 | +- interpolated string handlers |
| 94 | +- `global using` |
| 95 | +- file-scoped namespaces |
| 96 | +- extended property patterns |
| 97 | +- lambda natural type |
| 98 | +- explicit lambda return types |
| 99 | +- attributes on lambda expressions |
| 100 | +- `const` interpolated strings |
| 101 | +- `sealed` `ToString` in records |
| 102 | +- more accurate definite assignment and null-state analysis |
| 103 | +- mixed assignment and declaration in deconstruction |
| 104 | +- `AsyncMethodBuilder` on methods |
| 105 | +- `CallerArgumentExpression` |
| 106 | +- new `#line` format |
| 107 | + |
| 108 | +### C# 11 |
| 109 | + |
| 110 | +Typical pairing: `.NET 7` |
| 111 | + |
| 112 | +Key features: |
| 113 | + |
| 114 | +- raw string literals |
| 115 | +- generic math support |
| 116 | +- generic attributes |
| 117 | +- UTF-8 string literals |
| 118 | +- newlines inside interpolation expressions |
| 119 | +- list patterns |
| 120 | +- file-local types |
| 121 | +- required members |
| 122 | +- auto-default structs |
| 123 | +- matching `Span<char>` against a constant string |
| 124 | +- extended `nameof` scope |
| 125 | +- `nint` and `nuint` aliases |
| 126 | +- `ref` fields and `scoped ref` |
| 127 | +- improved method-group conversion to delegate |
| 128 | +- warning wave 7 |
| 129 | + |
| 130 | +### C# 12 |
| 131 | + |
| 132 | +Typical pairing: `.NET 8` |
| 133 | + |
| 134 | +Key features: |
| 135 | + |
| 136 | +- primary constructors |
| 137 | +- collection expressions |
| 138 | +- inline arrays |
| 139 | +- optional parameters in lambda expressions |
| 140 | +- `ref readonly` parameters |
| 141 | +- alias any type |
| 142 | +- `Experimental` attribute |
| 143 | +- interceptors as a preview feature |
| 144 | + |
| 145 | +### C# 13 |
| 146 | + |
| 147 | +Released November 2024. Stable on `.NET 9`. |
| 148 | + |
| 149 | +Key features: |
| 150 | + |
| 151 | +- `params` collections |
| 152 | +- new `lock` type and semantics with `System.Threading.Lock` |
| 153 | +- `\e` escape sequence |
| 154 | +- method-group natural type improvements |
| 155 | +- implicit indexer access in object initializers |
| 156 | +- `ref` locals and `unsafe` contexts in iterators and async methods |
| 157 | +- `ref struct` can implement interfaces |
| 158 | +- `allows ref struct` anti-constraint for generics |
| 159 | +- partial properties and partial indexers |
| 160 | +- overload resolution priority |
| 161 | +- `field` contextual keyword as a preview feature in C# 13-era tooling |
| 162 | + |
| 163 | +Practical use: |
| 164 | + |
| 165 | +- use `System.Threading.Lock` when the repo is on `.NET 9` and wants the newer synchronization model |
| 166 | +- use `params ReadOnlySpan<T>` or related collection forms in performance-sensitive APIs |
| 167 | +- use partial properties or indexers only where partial-type generation patterns already exist |
| 168 | +- do not assume `field` is safe unless the repo explicitly opted into preview behavior |
| 169 | + |
| 170 | +### C# 14 |
| 171 | + |
| 172 | +Released November 2025. Stable on `.NET 10`. |
| 173 | + |
| 174 | +Key features: |
| 175 | + |
| 176 | +- extension members |
| 177 | +- null-conditional assignment |
| 178 | +- `nameof` with unbound generic types such as `nameof(List<>)` |
| 179 | +- more implicit conversions for `Span<T>` and `ReadOnlySpan<T>` |
| 180 | +- modifiers on simple lambda parameters |
| 181 | +- field-backed properties via `field` |
| 182 | +- partial events and partial constructors |
| 183 | +- user-defined compound assignment operators |
| 184 | +- file-based app preprocessor directives |
| 185 | + |
| 186 | +Practical use: |
| 187 | + |
| 188 | +- use null-conditional assignment to remove boilerplate null guards when side effects are clear |
| 189 | +- use field-backed properties when you need simple validation in an otherwise auto-property style |
| 190 | +- use extension members when the repo wants richer extension APIs, not for cosmetic rewrites |
| 191 | +- use span-related improvements in performance-sensitive code paths, not as blanket style churn |
| 192 | + |
| 193 | +## Sources |
| 194 | + |
| 195 | +- [Configure C# language version](https://learn.microsoft.com/dotnet/csharp/language-reference/configure-language-version) |
| 196 | +- [The history of C#](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-version-history) |
| 197 | +- [What's new in C# 13](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-13) |
| 198 | +- [What's new in C# 14](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14) |
0 commit comments