跳到正文
DEV Community· Softinbit·· 3 小時前AI 評分56

現代 C# 速查表:C# 10 至 C# 14

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

AI 導讀

這份速查表整理 C# 10(.NET 6)至 C# 14(.NET 10)的語言功能,以用途説明和前後程式碼範例呈現。所有範例均以 .NET 10 SDK(10.0.300)編譯,涵蓋全域 using、原始字串、集合運算式、擴充成員及 field 關鍵字等功能。文中亦説明 C# 語言版本會按目標框架決定,並建議不要將 LangVersion 設為 latest。

正文

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.Total();
// C# 13: error CS1929: 'decimal[]' does not contain a definition for 'Total' ...
// C# 14: compiles and returns 30

nameof with unbound generics. No dummy type argument needed.

// Before
nameof(List<int>)        // "List"

// After
nameof(List<>)           // "List"
nameof(Dictionary<,>)    // "Dictionary"

Simple lambda parameters with modifiers. out, ref, in, scoped without spelling out types (params still needs them).

public delegate bool TryParser<T>(string text, out T result);

// Before
TryParser<decimal> p1 = (string text, out decimal result) => decimal.TryParse(text, out result);

// After
TryParser<decimal> p2 = (text, out result) => decimal.TryParse(text, out result);

Partial constructors and partial events. One defining and one implementing declaration, like partial methods.

public partial class Cart
{
    public partial Cart(int id);
    public partial event EventHandler? Changed;
}

public partial class Cart
{
    private EventHandler? _changed;
    public int Id { get; }
    public partial Cart(int id) { Id = id; }
    public partial event EventHandler? Changed
    {
        add => _changed += value;
        remove => _changed -= value;
    }
}

User-defined compound assignment. Overload += and ++ as instance operators that mutate in place.

// Before: static operator + creates a new object on every +=
// After
public sealed class Basket
{
    public decimal Total { get; private set; }
    public int Items { get; private set; }

    public void operator +=(decimal price) { Total += price; Items++; }
    public void operator ++() => Items++;
}

var basket = new Basket();
basket += 50m; basket += 25m; basket++;   // Total 75, Items 3

C# 14 also adds preprocessor directives for file-based apps (dotnet run app.cs).

Gotchas

  • field is now a keyword inside property accessors. A class with a member named field gets warning CS9258; write @field or this.field for the old member.
  • Primary constructor parameters are mutable captured state, not readonly fields. Using one both to initialize a field and directly in a method gives warning CS9124 (two copies).
  • var x = [1, 2]; fails with CS9176 "There is no target type for the collection expression." Give the variable a type.
  • Casting a Lock to object silently falls back to Monitor locking; the compiler warns with CS9216.
  • [Experimental] produces an error, not a warning, until the caller suppresses that exact ID.
  • required is a compile-time check on object initializers, not input validation.
  • An instance member always wins over an extension member with the same name.
  • ?.= skips the right side entirely when the target is null, including its side effects.

Copy-paste snippets

Solution-wide defaults. Put this next to your .sln; keep global usings in Store.Api.csproj so test projects still build.

<!-- File: Directory.Build.props -->
<Project>
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <!-- No LangVersion: net10.0 gives you C# 14 -->
  </PropertyGroup>
</Project>
<!-- File: Store.Api.csproj (inside <Project>) -->
<ItemGroup>
  <Using Include="Store.Api.Orders" />
  <Using Include="Store.Api.Products" />
</ItemGroup>

A request data transfer object (DTO) with required and field validation.

// File: Products/CreateProductRequest.cs
namespace Store.Api.Products;

public sealed class CreateProductRequest
{
    public required string Name
    {
        get;
        init => field = value.Trim();
    }

    public required decimal Price
    {
        get;
        init => field = value >= 0m
            ? value
            : throw new ArgumentOutOfRangeException(nameof(Price), "Price cannot be negative.");
    }

    public int Stock { get; init; }
}

A service with a primary constructor, collection expressions, and patterns.

// File: Orders/OrderPricing.cs
namespace Store.Api.Orders;

public sealed class OrderPricing(TimeProvider clock)
{
    private static readonly HashSet<DayOfWeek> Weekend = [DayOfWeek.Saturday, DayOfWeek.Sunday];

    public decimal ShippingFee(Order order) => order switch
    {
        { Lines.Count: 0 } => 0m,
        { Total: >= 500m } => 0m,
        _ when Weekend.Contains(clock.GetUtcNow().DayOfWeek) => 29.90m,
        _ => 49.90m
    };

    public static int[] ProductIds(Order order) =>
        [.. order.Lines.Select(l => l.ProductId).Distinct()];
}

Register it with builder.Services.AddSingleton(TimeProvider.System); and builder.Services.AddScoped<OrderPricing>();.

An HTML email body with a raw interpolated string.

// File: Infrastructure/Email/OrderEmailTemplates.cs
using System.Globalization;
using System.Net;
using Store.Api.Orders;

namespace Store.Api.Infrastructure.Email;

public static class OrderEmailTemplates
{
    private static readonly CultureInfo Turkish = CultureInfo.GetCultureInfo("tr-TR");

    public static string Confirmation(Order order, string customerName) => $$"""
        <h1>Thank you, {{WebUtility.HtmlEncode(customerName)}}</h1>
        <p>Order #{{order.Id}} is {{order.Status}}.</p>
        <p>Total: {{order.Total.ToString("C", Turkish)}}</p>
        """;
}

Output for order 1001 with a total of 1200 and the name Ayşe <VIP>:

<h1>Thank you, Ayşe &lt;VIP&gt;</h1>
<p>Order #1001 is Paid.</p>
<p>Total: ₺1.200,00</p>

Pass the result to IEmailService.SendAsync(to, subject, htmlBody, ct).

Related reading

  • "C# Pattern Matching Cheat Sheet"
  • "async/await Rules Cheat Sheet"
  • "Upgrading a Production API from .NET 8 to .NET 10: What Broke"
  • "C# Types in Practice: Value vs Reference, decimal vs double, and Nullable" (Zero to Full-Stack .NET)
  • "Methods, Parameters, Tuples, and Extension Methods" (Zero to Full-Stack .NET)

Tags: csharp, dotnet, csharp-14, dotnet-10, cheat-sheet


Originally published at www.softinbit.com. Part of The .NET Field Guide.

來源:DEV Community · dev.to