DevLazar
Note
067
Series
The Wire · Tooling
Date
Nov 10, 2026
Read
8 min · 1,588 words
Rev
1
Drawn by
L. Stanković · Niš
Lab notes / The Wire

.NET 11 and C# 15 Ship: Unions, In-Box Zstandard, and a Support Clock That Stops With .NET 10's

.NET 11 went GA on November 10 with C# 15 union types, Zstandard in System.IO.Compression and opt-in runtime async. It is an STS release whose support ends the same day as .NET 10 LTS.

Lazar StankovićEngineer, DevLazar · Niš, Serbia

.NET 11 reached general availability today, November 10, 2026: runtime 11.0.0 and SDK 11.0.100 are marked released in the dotnet/core metadata, following RC2 on October 13 and an RC3 with security fixes on October 25, and Visual Studio 18.12 bundles both. The timing matches the first day of .NET Conf 2026, November 10 to 13. It brings C# 15 union types, in-box Zstandard, and runtime async as an opt-in preview for your own code. It is a Standard Term Support release, and its end-of-support date is the detail most teams will plan around: November 14, 2028, the same day .NET 10 LTS expires. The devblogs "Announcing .NET 11" post had not yet appeared when this was written, so the What's new overview and the docs repositories are the record here.

C# 15: unions arrive and hierarchies close

C# 15 is the default language version for net11.0, and its headline is union types: public union Pet(Cat, Dog); declares a type that holds exactly one of its cases, converts implicitly from each, and gets exhaustiveness checking in switch. The closed modifier does the same for class hierarchies. A closed class is implicitly abstract, and a switch that handles every direct descendant needs no discard arm. The docs are candid about the catch: closure is not transitive, so intermediate classes must be marked closed too. Microsoft said at RC1 that C# 15 stabilizes unions, and neither feature needs LangVersion preview.

The block below shows both, plus collection expression arguments, where with(...) as the first element passes arguments to the collection's constructor.

Listing 1C#
1// Domain.cs (C# 15, the default for net11.0)
2public record class Cat(string Name);
3public record class Dog(string Name);
4public union Pet(Cat, Dog);
5
6public closed record class GateState;
7public record class Closed : GateState;
8public record class Open(float Percent) : GateState;
9
10public static class Domain
11{
12    public static string Describe(Pet pet) => pet switch
13    {
14        Cat c => $"cat {c.Name}",
15        Dog d => $"dog {d.Name}", // exhaustive, no discard arm
16    };
17
18    public static string Describe(GateState state) => state switch
19    {
20        Closed => "closed",
21        Open(var percent) => $"{percent}% open",
22    };
23
24    public static HashSet<string> Tags(string[] values) =>
25        [with(StringComparer.OrdinalIgnoreCase), .. values];
26}

Smaller additions: extension indexers, so numbers[2] can work on an IEnumerable<int>, and labeled break and continue for nested loops. A reworked memory-safety model, in which unsafe on a member obliges its callers, sits behind LangVersion preview and a feature flag. Read it; do not ship it.

Runtime async and Zstandard: the plumbing

Runtime async replaces compiler-generated state machines with suspension and resumption handled by the runtime. In .NET 11 the runtime libraries are compiled with it, so the BCL's async code already runs on it in every .NET 11 app. For your own assemblies it remains a preview opt-in, though net11.0 projects no longer need EnablePreviewFeatures. Microsoft's sample shows a live stack trace shrinking from 13 frames to 5, and continuations now skip ExecutionContext capture when there is no AsyncLocal<T> state to restore. The DOTNET_RuntimeAsync environment variable is gone; the per-project opt-out is <UseRuntimeAsync>false</UseRuntimeAsync>.

Listing 2XML
1<!-- Api.csproj -->
2<Project Sdk="Microsoft.NET.Sdk.Web">
3  <PropertyGroup>
4    <TargetFramework>net11.0</TargetFramework>
5    <Nullable>enable</Nullable>
6    <ImplicitUsings>enable</ImplicitUsings>
7    <!-- Preview: compile this project's async methods as runtime async -->
8    <Features>runtime-async=on</Features>
9  </PropertyGroup>
10</Project>

Zstandard is the library change backend engineers will touch first. ZstandardStream and ZstandardEncoder now live in System.IO.Compression beside GZipStream and BrotliStream, with the encoder and decoder shape Brotli has had for years. The reference assembly confirms the overloads used here: a CompressionLevel constructor, GetMaxCompressedLength, and span-based TryCompress, plus a ZstandardDictionary type for small, repetitive payloads.

Listing 3C#
1// Archive.cs (.NET 11)
2using System.IO.Compression;
3
4public static class Archive
5{
6    public static async Task CompressFileAsync(string source, string target)
7    {
8        await using FileStream input = File.OpenRead(source);
9        await using FileStream output = File.Create(target);
10        await using var zstd = new ZstandardStream(output, CompressionLevel.SmallestSize);
11        await input.CopyToAsync(zstd);
12    }
13
14    // One-shot and span-based, no Stream involved
15    public static byte[] Compress(ReadOnlySpan<byte> payload)
16    {
17        var buffer = new byte[ZstandardEncoder.GetMaxCompressedLength(payload.Length)];
18        return ZstandardEncoder.TryCompress(payload, buffer, out int written)
19            ? buffer[..written]
20            : throw new InvalidOperationException("Destination buffer too small.");
21    }
22}

The same release adds ZstandardCompressedContent for compressed HTTP request bodies, union support in System.Text.Json, LeftJoin, RightJoin and FullJoin in LINQ, IEEE 754 Decimal32, Decimal64 and Decimal128, and a run of process APIs such as Process.Signal(PosixSignal). The JIT gets its usual harvest, including a single-instruction Math.BigMul on x64.

ASP.NET Core 11 and EF Core 11 for backend work

ASP.NET Core 11 serializes C# unions anywhere System.Text.Json is used and describes them in OpenAPI as anyOf with no $type discriminator; they do not bind from route, query, header or form values. Zstandard joins the response-compression and request-decompression middleware and is enabled by default. Hosting now writes OpenTelemetry HTTP semantic-convention attributes onto the request activity, so OpenTelemetry.Instrumentation.AspNetCore is no longer needed; subscribe to the Microsoft.AspNetCore source.

EF Core 11 translates the new FullJoin to SQL FULL JOIN, adds MaxByAsync and MinByAsync, and exposes SQL Server's VECTOR_SEARCH() through VectorSearch(), experimental on both sides. dotnet ef database update --add scaffolds, compiles and applies a migration in one step, which suits containers that cannot rebuild. EF11 requires the .NET 11 runtime.

Listing 4C#
1// Program.cs (ASP.NET Core 11 + EF Core 11)
2// Packages: OpenTelemetry.Extensions.Hosting, OpenTelemetry.Exporter.Console
3// ShopContext (Customers, Orders, Blogs) is your EF Core model, registered elsewhere
4using System.IO.Compression;
5using Microsoft.AspNetCore.ResponseCompression;
6using Microsoft.EntityFrameworkCore;
7
8var builder = WebApplication.CreateBuilder(args);
9builder.Services.AddResponseCompression(); // zstd provider is on by default
10builder.Services.Configure<ZstandardCompressionProviderOptions>(o =>
11    o.CompressionOptions = new ZstandardCompressionOptions { Quality = 6 });
12builder.Services.AddOpenTelemetry().WithTracing(t => t
13    .AddSource("Microsoft.AspNetCore")
14    .AddConsoleExporter());
15
16var app = builder.Build();
17app.UseResponseCompression();
18
19app.MapGet("/value", () => new IntOrString(42)); // union, serialized by System.Text.Json
20
21app.MapGet("/report", async (ShopContext db) => await db.Customers
22    .FullJoin(db.Orders, c => c.Id, o => o.CustomerId,
23        (c, o) => new { Customer = c, Order = o }) // FULL JOIN
24    .ToListAsync());
25
26app.MapGet("/top-blog", async (ShopContext db) =>
27    await db.Blogs.MaxByAsync(b => b.Posts.Count()));
28
29app.Run();
30
31public union IntOrString(int, string);

What breaks on the way up

The compiler list is short and mostly about new keywords. Roslyn's breaking-change log records that union and closed are now contextual keywords in type declarations, so a type with either name needs an @ escape; with(...) inside a collection expression binds as constructor arguments; and nameof(this.P) in an attribute, accidentally legal since C# 12, is now an error. Span-typed collection expressions also got a narrower safe-context, which can surface new ref-safety errors in loops. Some of these already reached Visual Studio 18.3 and the 10.0.200 SDK.

Listing 5Diff
1// Legacy.cs: C# 14 to C# 15
2-class union { }
3-class closed { }
4+class @union { }
5+class @closed { }
6
7 class C
8 {
9-    union OldField;   // C# 15: parsed as a union declaration (CS9370)
10-    closed Other;     // C# 15: incomplete declaration (CS1519)
11+    @union OldField;
12+    @closed Other;
13     string P = "";
14
15-    object[] Items(object x, object y, object z) => [with(x, y), z]; // error
16+    object[] Items(object x, object y, object z) => [@with(x, y), z];
17     object with(object a, object b) => a;
18
19-    [System.Obsolete(nameof(this.P))] void M() { } // now disallowed
20+    [System.Obsolete(nameof(P))] void M() { }
21 }

ASP.NET Core's breaking-change list runs to 22 entries. The ones most likely to fail a build or a contract test: the ConcurrencyLimiter middleware is gone with no 11.x package to restore, so move to the rate limiter; OpenAPI output defaults to 3.2, so pin 3.1 if a client generator chokes; Microsoft.OpenApi moves to 3.x; MVC compatibility options are removed; Kestrel tightens HTTP protocol compliance; and response compression always emits Vary: Accept-Encoding.

Listing 6Diff
1// Program.cs: ASP.NET Core 10 to 11
2-using Microsoft.AspNetCore.ConcurrencyLimiter;
3-builder.Services.AddQueuePolicy(options =>
4-{
5-    options.MaxConcurrentRequests = 2;
6-    options.RequestQueueLimit = 25;
7-});
8+using System.Threading.RateLimiting;
9+builder.Services.AddRateLimiter(options =>
10+    options.AddConcurrencyLimiter("ConcurrencyPolicy", limiter =>
11+    {
12+        limiter.PermitLimit = 2;
13+        limiter.QueueLimit = 25;
14+        limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
15+    }));
16+builder.Services.AddOpenApi(options => // only if tooling cannot read 3.2 yet
17+    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1);
18
19 var app = builder.Build();
20-app.UseConcurrencyLimiter();
21+app.UseRateLimiter();
22+app.MapGet("/", () => "Hello World!").RequireRateLimiting("ConcurrencyPolicy");

Elsewhere in the .NET 11 list, x64 now requires x86-64-v2, adding SSE4.2 and POPCNT to the baseline, so a very old VM fails at startup with a missing-instruction-set message. When a BackgroundService fails under StopHost, IHost.RunAsync now rethrows instead of letting the process exit with success; Microsoft recommends doing nothing, and it is right. DateOnly.TryParse and TimeOnly.TryParse throw ArgumentException for invalid DateTimeStyles instead of returning false. On the SDK side, local container builds prefer platform-native runtimes before Docker, dnx scripts bypass global.json, and restore no longer searches the global packages folder for a higher version.

Twenty-four months, and the road from .NET 10

The release index ends .NET 11 support on November 14, 2028, the same day as .NET 10 LTS (released November 11, 2025). .NET 8 LTS and .NET 9 STS both leave support today. Counted from GA, that is 36 months for .NET 8 and .NET 10, and 24 for .NET 11.

Column chart of support windows from GA to end of support: .NET 8 LTS 36 months, .NET 10 LTS 36 months, .NET 11 STS 24 months, with .NET 10 and .NET 11 both ending November 14, 2028
Chart: Support window from GA to end of support, months. Source: dotnet/core releases index; .NET 11 release metadata.

Moving from 10 to 11 therefore buys features, not time. If you go:

  1. Change TargetFramework from net10.0 to net11.0, once in Directory.Build.props if you have one; C# 15 comes with it.
  2. Pin the SDK with the global.json below; latestFeature accepts later 11.0 feature bands and patches.
  3. Move images to mcr.microsoft.com/dotnet/sdk:11.0 and aspnet:11.0. The default aspnet:11.0 tag is Ubuntu 26.04, with Alpine 3.24 and Azure Linux 3.0 variants, so check any apt-get lines.
  4. In GitHub Actions, actions/setup-dotnet installs the global.json version when dotnet-version is omitted; set global-json-file if the file is not at the root.
  5. Grep for types named union or closed and for [with(, then build with warnings as errors.
  6. Replace ConcurrencyLimiter, choose OpenAPI 3.1 or 3.2, and update EF Core packages and the dotnet-ef tool to 11.0.0.
  7. Run integration tests that exercise host shutdown, and confirm your x64 hosts meet x86-64-v2.
Listing 7JSON
1{
2  "sdk": {
3    "version": "11.0.100",
4    "rollForward": "latestFeature"
5  }
6}

The 30,000-foot read

.NET 11 is a release about closing things. Unions and closed hierarchies close the old gap with F# and TypeScript for modeling values that are one of a known set; Zstandard closes a gap the ecosystem filled with native wrappers; runtime async begins to close the distance between the code you write and the stack you debug. None of it is spectacular, which is its virtue. The breaking changes are mostly the price of new keywords and old deprecations finally collected.

The calendar is the part you cannot negotiate with. With STS at 24 months, .NET 10 and .NET 11 expire together, and the old habit of skipping odd releases has lost its arithmetic. The remaining question is whether unions, in-box zstd and cleaner stacks justify one more upgrade before November 2028. For a service that models domain states with marker interfaces and default arms, probably yes. Watch whether runtime async sheds its preview label in .NET 12; that is where the performance story will be told.

Sources: .NET 11 release metadata, dotnet/core (GA date, SDK 11.0.100, STS, RC2 and RC3 dates); .NET 11.0.0 release notes (Visual Studio 18.12, Docker images, dotnet-ef 11.0.0); dotnet/core releases index (end-of-support dates for .NET 8, 9, 10 and 11); .NET Conf 2026 (November 10 to 13 launch event); .NET Blog (no GA announcement post yet); Announcing .NET 11 RC1 (unions stabilized); What's new in .NET 11 (feature overview, process APIs); What's new in C# 15 (unions, closed, collection arguments, extension indexers, labeled loops, memory safety preview); .NET 11 runtime (runtime async, stack traces, JIT); .NET 11 libraries (Zstandard, LINQ joins, decimal types, HTTP content); System.IO.Compression reference source (Zstandard signatures); ASP.NET Core 11 (unions, zstd middleware, OpenTelemetry, OpenAPI 3.2); EF Core 11 (FullJoin, MaxByAsync, VectorSearch, migrations); Roslyn breaking changes for .NET 11 (compiler changes and fixes); .NET 11 breaking changes (runtime, library and SDK list); Minimum hardware requirements (x86-64-v2); IHost.RunAsync change (BackgroundService failures); DateOnly and TimeOnly TryParse change (ArgumentException); ASP.NET Core 11 breaking changes (22 entries); ConcurrencyLimiter removed (rate limiter migration); OpenApiVersion defaults to 3.2 (pinning 3.1); dotnet-docker aspnet README (11.0 tags, Ubuntu 26.04); global.json docs (rollForward); setup-dotnet action.yml (global.json inputs). CAVEAT: GA status rests on the dotnet/core metadata, release notes and container tags dated November 10; the devblogs announcement had not been published when checked, and the 11.0.0 release notes still list their CVE entries as "TBD". The Learn overview was last revised for RC2, the EF Core 11 page still calls the release "in development", and the ASP.NET Core breaking-change list is dated September 29, so details may shift. Runtime async for user code, C# memory safety and EF Core VectorSearch are preview or experimental. Support months are computed from official GA and end-of-support dates. Corrections to earlier coverage: GA followed an RC3, not RC2; runtime async is not yet a stable opt-in for application code; Zstandard now sits in the System.IO.Compression namespace. No vendor performance figures are cited because no .NET 11 performance post was found.

Lazar Stanković
Engineer and writer, running a one-man shop in Niš, Serbia. I build systems that survive their launch and write down what the work teaches.
Related notes
Note 069 · The Wire · 7 min
Node.js 26 'Lithium' Goes LTS: Temporal Ships, and the Deprecation Bills Come Due
Node.js 26.13.0 'Lithium' became Active LTS on October 28, with support to April 2029. Temporal by default, V8 14.6, real removals, and what upgrading a NestJS or Express service actually takes.
Note 068 · The Wire · 8 min
NestJS 12: ESM Packages, Schema-First Validation, and an Upgrade You Can Mostly Script
NestJS 12 shipped August 27 with ESM-only packages, Standard Schema validation on route decorators, Vitest and oxlint defaults, and a hosted observability SDK. What breaks, what does not, and how to move a v11 app.
Note 072 · The Wire · 16 min
Vercel, Netlify, Cloudflare, Coolify, Dokploy: The Limits, the Meters and the Monthly Bill
CPU caps, memory ceilings, build limits and egress rates for five deploy targets, from their own docs as of November 11, 2026, plus two worked monthly bills. The meters differ more than the sticker prices.
← Note 066 · SoftBank's ¥1.1 Trillion Quarter: The Write-Off Was Made of Turbines, Not ModelsNote 068 · NestJS 12: ESM Packages, Schema-First Validation, and an Upgrade You Can Mostly Script →
The Wire · Weekly · Free
Notes like this one, every week.

AI infrastructure, shipping on a deadline, and the craft of building things that last. No noise.