C# adopted an annual release cadence starting with .NET 6, released in November 2021. Five versions over five years: C# 10, 11, 12, 13, and 14. All maintained backward compatibility. For .NET teams, this means the compiler shipped new features every November, but many projects stayed on older language versions by default. Mapping what each version delivered is the first step to knowing what is available and what can still be adopted.
C# 10: ceremony reduction and string performance
C# 10 (Nov. 2021) was about ceremony reduction. File-scoped namespace declarations eliminated the nested block every file carried. Global using directives allowed centralizing common imports at the project level, instead of repeating the same using statements in every file. Record structs extended to structs the member synthesis and with expressions that record classes already had.
Lambdas gained natural types, explicit return type declarations, and attribute support. Interpolated string handlers opened the door to custom interpolation implementations, a feature the .NET 6 runtime used to eliminate allocations in logging paths. CallerArgumentExpression enabled capturing an argument expression as a string at compile time, useful for assertion messages and diagnostics.
C# 11: generic math and struct expressiveness
C# 11 (Nov. 2022) was defined by generic math. Through static abstract members in interfaces, it became possible to write numeric algorithms once and reuse them for any type that implements the interface. The .NET 7 base class library used this feature to unify its numeric APIs.
Required members made property initialization verifiable at compile time. Auto-default structs simplified default value semantics. ref fields with scoped ref brought fine-grained control over reference lifetimes. Raw string literals eliminated escape sequences in multiline strings. UTF-8 string literals allow literals encoded directly as UTF-8 without runtime conversion. File-local types make a type private to its compilation file, useful for source generators. List patterns expanded pattern matching to collection decomposition. Generic attributes, in preview since C# 10, were finalized.
C# 12: primary constructors and inline arrays
C# 12 (Nov. 2023) generalized primary constructors to any class or struct, not just records. Constructor parameters become accessible throughout the type body, eliminating the need to declare explicit backing fields just to store construction values.
Collection expressions introduced a unified creation syntax with the spread operator (..) for expanding existing collections. Inline arrays allow declaring a fixed-size array embedded in a struct without heap allocation, useful for high-performance buffers. Lambdas gained default parameter values. ref readonly parameters added clarity to APIs needing read-only by-reference parameters. Using aliases now accept any type, not just named types.
C# 13: expanded params, modernized lock, and ref structs in generics
C# 13 (Nov. 2024) extended the params modifier to any recognized collection type, not just arrays. This simplifies APIs that need to accept Span, ReadOnlySpan, or collection interfaces without method overloads.
System.Threading.Lock received dedicated compiler semantics. When a lock statement targets this type, the compiler generates code using the EnterScope() method, returning a ref struct that supports the Dispose() pattern for deterministic scope exit. ref struct types can now implement interfaces and serve as generic type arguments. Partial properties and indexers were added to the partial member support. Overload resolution priority lets library authors designate a preferred overload, guiding consumers toward more efficient implementations. ref locals and unsafe contexts are now allowed in async methods and iterators.
C# 14: extension members and the field keyword
C# 14 (Nov. 2025) delivered extension members, which generalize extension methods to include properties, operators, and other instance members. The concept of extension, until now limited to methods, applies to any kind of member.
The field keyword eliminates the need for a separate backing field in properties with custom logic. Instead of declaring a private field separately, the property body can reference field directly. Null-conditional assignment simplifies conditional assignment on null. User-defined compound assignment operators allow types to define the behavior of operators such as +=. nameof now accepts unbound generic types, useful for diagnostic messages and reflection. Partial events and constructors complete the support for partial members in partial types.
What this means for the team
Five versions in five years followed a coherent pattern: each release closed more of the gap between what the programmer must write and what the compiler can infer, without changing the underlying execution model. The features are not independent: generic math in C# 11 prepared the ground for more expressive generic collections in C# 12 and 13; extension members in C# 14 complete what extension methods started in C# 3.
The practical first step is to check what language version is declared in the csproj. Many projects stay on older versions because the default version follows the target framework, and updating the framework has testing costs that updating just the language version does not. The compiler will flag simplification opportunities available in the new version. What the team works on and what is in production determines what makes sense to adopt. Analyzing each feature in the context of the project is the right approach before making broad adoption decisions.
Source: The history of C#
Top comments (0)