Modern C# Cheat Sheet: C# 10 to C# 14

2026-11-04来源 dev.to C#English

Note: this article was drafted with AI assistance and reviewed by Softinbit Technology before publishing. It is part of the series The .NET Field Guide.

Every language feature from .NET 6 to .NET 10, each with a one-line purpose and a before/after snippet.

Part of "The .NET Field Guide". Section: Cheat Sheets.

TL;DR

  • Every C# feature from C# 10 (.NET 6) through C# 14 (.NET 10) condensed into single-purpose before/after snippets tested on .NET 10 SDK (10.0.300).
  • Fast lookups for top idioms: file-scoped namespaces, raw strings, required properties, primary constructors, collection expressions, Lock, and field-backed properties.
  • Clear guidance on target framework defaults and compiler diagnostics (#error version) without fragile LangVersion overrides.

Level: Intermediate · Prerequisites: Working knowledge of C# 9 / .NET 5 or earlier · Reading time: 13 min

Your team just moved Store.Api to .NET 10, and a reviewer comments "use field here" on a property you wrote the C# 9 way. This sheet is for developers who learned C# a few versions ago and want the short version of C# 10 (.NET 6) through C# 14 (.NET 10). Every snippet was compiled with the .NET 10 software development kit (SDK), version 10.0.300; outputs and error messages are real.

At a glance

C# Ships with Released Default for Headline features
10 .NET 6 November 2021 net6.0 global usings, file-scoped namespaces, record structs
11 .NET 7 November 2022 net7.0 raw strings, required, list patterns, generic math
12 .NET 8 November 2023 net8.0 primary constructors, collection expressions
13 .NET 9 November 2024 net9.0 params collections, Lock, partial properties
14 .NET 10 November 2025 net10.0 extension members, field, ?.= assignment

The lookups people make most:

I want to... Write Since
Drop the namespace indent namespace Store.Api.Orders; 10
Import a namespace once per project global using Store.Api.Orders; 10
Paste JavaScript Object Notation (JSON) or SQL without escaping """...""" and $$"""...{{x}}...""" 11
Force callers to set a property public required string Name { get; init; } 11
Inject dependencies without fields class OrderService(AppDbContext db) 12
Create an array or list int[] ids = [1, 2, 3]; and [.. a, .. b] 12
Accept many arguments without an array params ReadOnlySpan<string> tags 13
Lock without new object() private readonly Lock _gate = new(); 13
Validate in a setter, no backing field set => field = value.Trim(); 14
Add a property to a type you do not own extension(Product p) { public bool InStock => ... } 14
Assign only when the target is not null order?.Status = OrderStatus.Paid; 14
Get "List" from a generic type nameof(List<>) 14

Which version you get (LangVersion)

The compiler picks the default C# version from your target framework (the "Default for" column above). You do not need LangVersion in the project file.

  • Microsoft's documentation states that using a C# version newer than the default for your target framework is not supported. Upgrade the target framework instead.
  • Do not set <LangVersion>latest</LangVersion>. Its meaning changes with whatever SDK is installed on each machine.
  • default ignores the target framework, so it is not the same as leaving LangVersion out.
  • To see what you are compiling with, put #error version in any file:
error CS8304: Compiler version: '5.6.0-2.26230.102 (caa81fa4...)'. Language version: 14.0. 

Using a C# 14 feature when the language version is 13 (for example net9.0 built with the .NET 10 SDK):

error CS9260: Feature 'field keyword' is not available in C# 13.0. Please use language version 14.0 or greater. 

C# 10 (.NET 6)

Global usings. Import a namespace once for the whole project.

// Before: repeated at the top of every file using Store.Api.Orders; // After: once, in GlobalUsings.cs (or <Using Include="Store.Api.Orders" /> in the .csproj) global using Store.Api.Orders; 

File-scoped namespaces. One less indentation level per file.

// Before namespace Store.Api.Orders { public sealed class OrderService { } } // After namespace Store.Api.Orders; public sealed class OrderService { } 

Record structs. Value-type records with generated equality, ToString, and with.

// Before: a struct with hand-written Equals, GetHashCode, ==, and ToString // After public readonly record struct Money(decimal Amount, string Currency); var a = new Money(10m, "TRY"); var b = a with { Amount = 12m }; Console.WriteLine(a); // Money { Amount = 10, Currency = TRY } Console.WriteLine(a == new Money(10m, "TRY")); // True 

Extended property patterns. Reach into nested members with a dot.

// Before if (order is { Lines: { Count: > 0 } }) { } // After if (order is { Lines.Count: > 0 }) { } 

Constant interpolated strings. Build const strings, for example route templates, with $"".

// Before public const string Products = Base + "/products"; // After public const string Base = "/api"; public const string Products = $"{Base}/products"; // "/api/products", usable in [Route(Products)] 

Lambda improvements. Lambdas get a natural type, an optional explicit return type, and attributes.

// Before Func<string, int> parse = s => int.Parse(s); // After var parse = (string s) => int.Parse(s); // inferred as Func<string, int> var pick = object (bool b) => b ? 1 : "one"; // explicit return type app.MapGet("/api/health", [AllowAnonymous] () => Results.Ok()); // Microsoft.AspNetCore.Authorization 

C# 11 (.NET 7)

Raw string literals. Multi-line text with quotes and braces, no escaping.

// Before var json = "{ \"id\": " + id + ", \"name\": \"Mouse\" }"; // After: $$ means {{x}} interpolates and a single { is literal var json = $$"""  { "id": {{id}}, "name": "Mouse" } """; // { "id": 42, "name": "Mouse" } 

Required members. The compiler forces object initializers to set these properties.

public sealed class CreateProductRequest { public required string Name { get; init; } public required decimal Price { get; init; } } var request = new CreateProductRequest { Name = "Mouse" }; // error CS9035: Required member 'CreateProductRequest.Price' must be set in the object initializer or attribute constructor. 

Mark a constructor that sets them all with [SetsRequiredMembers] from System.Diagnostics.CodeAnalysis.

List patterns. Match arrays and lists by shape, with .. for "the rest".

// Before: if (ids.Length == 0) ... else if (ids.Length == 1) ... // After static string Describe(int[] ids) => ids switch { [] => "empty", [var only] => $"one: {only}", [var first, .., var last] => $"{first}..{last}", }; // Describe([1, 2, 3]) -> "1..3" 

Generic math (static abstract interface members). Write one numeric algorithm for every number type.

using System.Numerics; // Before: Sum(IEnumerable<int>), Sum(IEnumerable<decimal>), ... // After static T Sum<T>(IEnumerable<T> values) where T : INumber<T> { T total = T.Zero; foreach (var v in values) total += v; return total; } // Sum([10.5m, 20m]) -> 30.5 Sum([1, 2, 3]) -> 6 

Your own interfaces can declare static abstract TSelf Zero { get; } the same way.

UTF-8 string literals. Get bytes at compile time.

// Before byte[] ok = Encoding.UTF8.GetBytes("OK"); // After ReadOnlySpan<byte> ok = "OK"u8; // 2 bytes, no runtime encoding 

File-local types. A type visible only inside its own file.

// Before: internal helper visible to the whole assembly // After file sealed class SkuFormatter { public static string Format(int id) => $"SKU-{id:D5}"; // SKU-00042 } 

scoped. Promise that a span parameter does not escape, so callers can pass stack memory.

public ref struct ReceiptWriter { private Span<char> _dest; public int Written; public ReceiptWriter(Span<char> dest) { _dest = dest; Written = 0; } public void Append(scoped ReadOnlySpan<char> text) { text.CopyTo(_dest[Written..]); Written += text.Length; } } // Without "scoped", w.Append(stackallocBuffer) fails with error CS8350. 

C# 12 (.NET 8)

Primary constructors for classes. Constructor parameters in the class header, in scope everywhere.

// Before public sealed class PriceCalculator { private readonly decimal _vatRate; public PriceCalculator(decimal vatRate) => _vatRate = vatRate; } // After public sealed class PriceCalculator(decimal vatRate) { public decimal WithVat(decimal net) => net * (1 + vatRate); // 100 -> 120.00 } 

Collection expressions. One syntax for arrays, lists, spans, and sets, plus .. spread.

// Before var ids = new int[] { 1, 2, 3 }; var tags = new List<string> { "new", "sale" }; var none = Array.Empty<Product>(); // After int[] ids = [1, 2, 3]; List<string> tags = ["new", "sale"]; string[] all = [.. tags, "featured"]; // new, sale, featured Product[] none = []; 

Default lambda parameters. Optional parameters on lambdas.

// Before: a local function or two overloads // After var discount = (decimal price, decimal rate = 0.10m) => price * (1 - rate); discount(100m); // 90.00 discount(100m, 0.25m); // 75.00 

Alias any type. using aliases for tuples, arrays, and pointer types.

// Before: only named types could be aliased // After using LineTotal = (int ProductId, decimal Total); LineTotal line = (5, 59.90m); Console.WriteLine(line.Total); // 59.90 

Inline arrays. A fixed-size buffer inside a struct, without unsafe.

using System.Runtime.CompilerServices; [InlineArray(3)] public struct LastThreePrices { private decimal _element0; } var last = new LastThreePrices(); last[0] = 10m; last[1] = 20m; last[2] = 30m; // indexable and foreach-able 

Experimental attribute. Mark APIs that callers must opt into.

using System.Diagnostics.CodeAnalysis; [Experimental("STORE001")] public static class Recommendations { public static string[] For(int userId) => ["Mouse", "Keyboard"]; } // Calling it from Store.Api.Products without suppression: // error STORE001: 'Store.Api.Products.Recommendations' is for evaluation purposes only and is // subject to change or removal in future updates. Suppress this diagnostic to proceed. 

Suppress with #pragma warning disable STORE001 or <NoWarn>STORE001</NoWarn>. Also new in C# 12: ref readonly parameters, and interceptors as a preview feature.

C# 13 (.NET 9)

params collections. params works with spans and collection interfaces, not only arrays.

// Before: allocates an array on every call public static string Tags(params string[] tags) => string.Join("|", tags); // After public static string Tags(params ReadOnlySpan<string> tags) => string.Join("|", tags); public static int CountIds(params IEnumerable<int> ids) => ids.Count(); // Tags("new", "sale") -> "new|sale" 

System.Threading.Lock. A dedicated lock type; lock uses its API instead of Monitor.

// Before private readonly object _gate = new(); // After private readonly Lock _gate = new(); lock (_gate) // compiles to _gate.EnterScope() { _reserved += qty; } 

\e escape. The ESCAPE character (U+001B) for terminal colors.

// Before Console.WriteLine("\u001b[32mPaid\u001b[0m"); // After Console.WriteLine("\e[32mPaid\e[0m"); 

Partial properties and indexers. Declare in one part, implement in another (source generators use this).

public partial class ProductView { public partial string Sku { get; set; } // declaring part } public partial class ProductView { private string _sku = ""; public partial string Sku // implementing part { get => _sku; set => _sku = value.ToUpperInvariant(); } } 

Implicit index access in object initializers. ^ (from the end) works inside initializers.

public sealed class Shelf { public int[] Slots { get; } = new int[5]; } // Before: front indexes only, { Slots = { [4] = 99 } } // After var shelf = new Shelf { Slots = { [^1] = 99, [0] = 1 } }; // 1,0,0,0,99 

OverloadResolutionPriority. Library authors steer callers to a better overload without breaking them.

using System.Runtime.CompilerServices; public static class Logging { public static string Log(string message) => "string overload"; [OverloadResolutionPriority(1)] public static string Log(ReadOnlySpan<char> message) => "span overload"; } // Logging.Log("hello") -> "span overload" 

ref struct interfaces and allows ref struct. Span-holding types can implement interfaces and flow through generics.

public interface ISkuSource { int Count { get; } } public ref struct SpanSkuSource : ISkuSource { private readonly ReadOnlySpan<string> _skus; public SpanSkuSource(ReadOnlySpan<string> skus) => _skus = skus; public int Count => _skus.Length; } static int CountOf<T>(T source) where T : ISkuSource, allows ref struct => source.Count; 

Also in C# 13: ref locals and unsafe blocks inside async methods and iterators (not across await or yield), and smarter method group overload resolution.

C# 14 (.NET 10)

Extension members. Extension blocks add properties, methods, and static members to types you do not own.

// Before: extension methods only public static bool InStock(this Product p) => p.Stock > 0; // After public static class ProductExtensions { extension(Product product) { public bool InStock => product.Stock > 0; public decimal PriceWithVat(decimal rate) => product.Price * (1 + rate); } extension(Product) { public static Product Placeholder => new() { Name = "(none)" }; } } // p.InStock -> True p.PriceWithVat(0.20m) -> 120.00 Product.Placeholder.Name -> (none) 

The field keyword. Use the compiler-generated backing field inside an accessor.

// Before private string _email = ""; public string Email { get => _email; set => _email = value.Trim().ToLowerInvariant(); } // After public string Email { get; set => field = value.Trim().ToLowerInvariant(); } = ""; // " Ali@Example.COM " -> "ali@example.com" 

Null-conditional assignment. ?. and ?[] on the left side of = and compound assignments.

// Before if (order is not null) order.Status = OrderStatus.Paid; // After order?.Status = OrderStatus.Paid; // right side runs only when order is not null order?.Total += 10m; // order?.Total++; -> error CS1059 (++ and -- are not allowed here) 

Implicit span conversions. Arrays convert to spans for extension receivers and generic inference.

public static decimal Total(this ReadOnlySpan<decimal> prices) { decimal sum = 0; foreach (var p in prices) sum += p; return sum; } decimal[] prices = [10m, 20m]; prices.

原文:https://www.a8q9.cn/en/article/modern-c-cheat-sheet-c-10-to-c-14-e154dd.html

相关文章