.NET 11 Preview 1 — What’s New, What’s Exciting, and What Still Feels Rough Around the Edges
When Microsoft announced the first preview of .NET 11 last week, the usual mix of “here we go again” and “let’s see what they finally fixed” rippled through the .NET community. I’ve been writing about .NET since the days when “Core” was still a buzzword, so I read the blog post, skimmed the release notes, and then spent a solid afternoon poking around the preview in a fresh console app.
Below is my attempt to turn the flood of technical bullet points into a story you can actually follow—whether you’re a seasoned backend engineer, a hobbyist building Blazor widgets, or just someone who likes to know what the next big thing in the Microsoft stack looks like.
Spoiler: The headline feature is Runtime Async, a change that feels like the runtime finally decided to stop pretending it doesn’t understand the async/await sugar we’ve been feeding it for a decade.
A Quick Reality Check
First, the basics.
| Item | Detail |
|---|---|
| Release | .NET 11 Preview 1 (released Oct 2024) |
| Support model | Standard Term Support (STS), GA slated for Nov 2026 |
| Scope | Runtime, SDK, libraries, C# 15, F#, ASP.NET Core, Blazor, .NET MAUI, and a first‑look at CoreCLR‑on‑WebAssembly |
| Where to read the official word | Microsoft’s dev blog, GitHub release notes, and the .NET docs site |
If you’re already on .NET 8 LTS, you can install the preview side‑by‑side with dotnet-install.ps1 or the Visual Studio preview channel. The preview is enabled by default for CoreCLR, which means you don’t have to flip any environment variables to start playing with the new runtime features.
Runtime Async: The Runtime Finally Gets the Joke
The Problem We’ve Lived With
Since C# 5 introduced async/await, the compiler has been the only entity that knows how to turn an async method into a state machine. The generated struct holds the locals, the current “step” (the “await” you’re paused at), and the continuation delegate that gets called when the awaited Task completes.
That works fine for most scenarios, but it also means the runtime is blind to the fact that a method is asynchronous. It can’t, for example, introspect the call stack and give you a clean “async stack trace” beyond the first await. You’ve probably seen those cryptic “[Task]” frames littering your logs after a few awaits.
What Runtime Async Changes
In .NET 11 Preview 1, Microsoft rewrites that story. The runtime now treats async methods as a first‑class concept. Instead of the compiler doing all the heavy lifting, the runtime understands the suspension points and can:
- Suspend and resume a method without relying on the compiler‑generated state machine.
- Preserve a more accurate call stack across awaits, which should make debugging async code feel less like deciphering a treasure map.
- Potentially reduce allocation overhead, because the runtime can reuse existing structures rather than always allocating a new state‑machine struct.
The feature is called Runtime Async (see the release notes here). In this preview, CoreCLR ships with the feature enabled by default, so you can start experimenting right away. However, none of the core libraries have been recompiled to use the new runtime‑async code paths yet. That’s why you’ll see a warning if you try to run the preview on existing libraries—they’ll still fall back to the classic compiler‑generated state machines.
If you want to see Runtime Async in action today, you have to:
-
Enable preview features in your project:
<PropertyGroup> <EnablePreviewFeatures>true</EnablePreviewFeatures> </PropertyGroup> -
Add the compiler flag that tells the compiler to emit the “runtime‑async” attribute:
<ItemGroup> <AdditionalFiles Include="runtimeasync.csproj" /> </ItemGroup>
(Full details are in the preview notes; the flag is -runtime-async.)
My First Impressions
I took a simple console app that spawns a few Task.Delay calls, enabled Runtime Async, and ran it under the debugger. The stack trace now shows each await as a distinct frame, complete with method names. No more “[Task]” placeholders. It’s a subtle win, but for anyone who has ever stared at a stack trace that looks like a broken telephone game, it’s a breath of fresh air.
That said, the implementation is still early. The preview notes admit that performance numbers are “in flux”—the JIT has to add a new path for suspending methods, and there are edge cases (e.g., async iterators) that haven’t been fully vetted. Expect a few hiccups before the final release.
Native AOT Gets a Boost
Another runtime‑level change worth noting is Native AOT support for CoreCLR in this preview. Historically, Native AOT (the ability to compile a .NET app into a single native binary) lived under the “Mono” umbrella, which meant you had to juggle two runtimes for the same platform.
Now CoreCLR can emit native images directly, which simplifies the toolchain and brings the performance gains of AOT to the mainstream runtime. The preview doesn’t yet ship with a full AOT pipeline, but the groundwork is laid, and you’ll see the dotnet publish -c Release -r win-x64 -p:PublishAot=true flag start to work without pulling in the Mono runtime.
Libraries Getting Their Own Makeover
Zstandard Compression – A New ZstandardStream
If you’ve ever had to compress large blobs in a microservice, you know the pain of balancing speed, memory usage, and compression ratio. .NET 11 introduces a native Zstandard (zstd) implementation via the ZstandardStream class. It’s a thin wrapper around the official C library, which means you get the same speed and compression quality you’d see in tools like zstd or tar with --zstd.
// Compress data using ZstandardStream
using var compressStream = new ZstandardStream(outputStream, CompressionMode.Compress);
await inputStream.CopyToAsync(compressStream);
// Decompress data
using var decompressStream = new ZstandardStream(inputStream, CompressionMode.Decompress);
await decompressStream.CopyToAsync(outputStream);
The API mirrors GZipStream and DeflateStream, so you can swap it in with minimal code changes. Early benchmarks (shared by a few community members on the .NET Discord) suggest 30‑40 % faster compression and roughly half the memory pressure compared to GZipStream on the same data set.
BFloat16 – A Tiny Float for Big AI
Machine‑learning workloads love “half‑precision” floats because they cut memory bandwidth in half while still giving acceptable numeric fidelity. .NET 11 adds a BFloat16 struct, which mirrors the 16‑bit floating‑point format used by Google’s TPUs and many modern GPUs.
Why not just use Half? Half is IEEE‑754 binary16, which has a smaller dynamic range. BFloat16 keeps the exponent size of a 32‑bit float but truncates the mantissa, making it more tolerant of overflow/underflow—exactly what you need for deep‑learning tensors.
You’ll see BFloat16 pop up in the System.Numerics.Tensors package and in the upcoming ML.NET extensions. If you’re already fiddling with ONNX models in C#, you can now map the model’s float16 inputs directly to BFloat16 without a custom conversion layer.
Crypto APIs – HMAC & KMAC Verification
Security never gets old, and .NET 11 adds first‑class HMAC and KMAC verification methods to the System.Security.Cryptography namespace. The new overloads let you verify a MAC in a single pass, without having to allocate an intermediate buffer for the computed tag.
bool verified = HMACSHA256.VerifyHash(key, message, expectedTag);
The addition is subtle but valuable for high‑throughput services that need to validate thousands of requests per second. The API mirrors the HashAlgorithm pattern you already know, so the learning curve is near zero.
Happy Eyeballs in Socket.ConnectAsync
If you’ve ever watched a client stall while trying to connect to an IPv6‑only endpoint, you’ve felt the pain of “Happy Eyeballs” (RFC 8305). .NET 11 finally brings Happy Eyeballs support to Socket.ConnectAsync, meaning the runtime will try both IPv4 and IPv6 in parallel and use whichever connects first.
In practice, this translates to faster, more reliable connections for cloud‑native apps that run in mixed‑IP environments. No more “my service works locally but hangs in production” mysteries.
Language Updates: C# 15 and F# Gets a Parallel Boost
C# 15 – Collection Expression Arguments & Layout Tweaks
C# 15 lands with a handful of ergonomics that feel like the language finally listening to the “real‑world” use cases that have been bubbling up on GitHub. The headline feature is collection expression arguments.
Instead of writing:
var list = new List<int> { 1, 2, 3 };
Process(list);
You can now do:
Process([1, 2, 3]); // collection expression argument
It’s a tiny syntactic sugar, but it cuts down boilerplate in data‑pipeline code where you often just need to pass a short list of literals.
The other notable addition is extended layout support for struct types, allowing you to define custom memory layouts with fieldoffset attributes more cleanly. This is aimed at interop scenarios (think high‑frequency trading or low‑level graphics) where you need precise control over struct packing.
F# – Parallel Compilation by Default
F# has been quietly working on parallel compilation for years, and .NET 11 finally flips the switch. The compiler now builds project files in parallel where possible, shaving off several seconds from the typical dotnet build time for medium‑size solutions.
The change is transparent—you won’t need to add any flags. If you’re using the new dotnet fsi REPL, you’ll notice the startup is a bit snappier too.
Platform‑Specific Highlights
.NET MAUI – XAML Source Generation By Default
If you’ve tried building a cross‑platform UI with MAUI, you know the XAML compilation step can be a bottleneck, especially on CI pipelines. In .NET 11 Preview 1, XAML source generation is now enabled by default. The build process translates XAML into C# at compile time, eliminating the runtime parsing step.
The result? Faster startup and smaller app packages. The trade‑off is that you lose the ability to edit XAML at runtime (which most apps never needed anyway).
CoreCLR Becomes the Default Android Runtime
Historically, MAUI Android builds used the Mono runtime. Starting with this preview, CoreCLR is the default for release builds. This aligns Android with the rest of the .NET ecosystem and brings the same JIT optimizations you get on Windows and Linux.
If you’re targeting Android, you’ll notice improved startup and better memory usage in release builds. Debug builds still use Mono for the hot‑reload experience.
Interactive dotnet run Target‑Framework & Device Selection
Running a .NET app on a specific device used to be a two‑step dance: first set the --framework flag, then pass a device identifier. The new preview adds an interactive prompt when you invoke dotnet run without those arguments. The CLI will list the available frameworks (e.g., net11.0, net8.0) and any connected devices (iOS simulators, Android emulators) and let you pick one with a simple number entry.
It’s a tiny quality‑of‑life win that feels like the CLI finally grew a personality.
New SDK Analyzers & Hot Reload Improvements
The SDK now ships with additional Roslyn analyzers that catch common pitfalls in async code (e.g., forgetting to ConfigureAwait(false) in library code).
Hot Reload, which lets you edit code while the app is running, now supports project‑reference updates. In other words, if you change a library that your main project references, the changes propagate without a full rebuild. It’s a small thing that makes the “edit‑save‑see‑the‑change” loop feel genuinely instantaneous.
GC Heap Hard Limits for 32‑bit Processes
A long‑standing limitation of 32‑bit processes was that the GC could grow the heap until the OS refused more memory, often resulting in obscure OOM crashes. .NET 11 adds hard limits on the GC heap for 32‑bit processes, making those crashes deterministic and easier to diagnose. If you need more memory, the recommendation is to switch to a 64‑bit build—something most modern servers already do.
ASP.NET Core & Blazor: The Little Things That Add Up
Blazor’s New EnvironmentBoundary Component
Blazor now has a EnvironmentBoundary component that mirrors the MVC environment tag helper. You can wrap UI fragments that should only render in Development or Production:
<EnvironmentBoundary Environment="Development">
<p>Debug toolbar goes here.</p>
</EnvironmentBoundary>
It’s a tidy way to keep environment‑specific UI out of the main component tree without resorting to #if DEBUG directives.
IHostedService in Blazor WebAssembly
Background services have been a gray area in Blazor WebAssembly because the browser sandbox doesn’t expose a “process” to host long‑running work. The preview adds IHostedService support, allowing you to register services that start when the app loads and run in the background (e.g., periodic polling, WebSocket keep‑alive).
The runtime wires these services into the Blazor IHost pipeline, so you can inject IHostedService implementations just like you would on the server side.
Environment Variables in the Browser
You can now read environment variables via IConfiguration in a Blazor WebAssembly app. The values are injected at build time, but the new API also respects runtime overrides that you can supply through a JSON file served alongside the WASM bundle. This means you can change API endpoints or feature flags without rebuilding the entire app—handy for A/B testing.
QuickGrid Row‑Click Events & New Form Components
The QuickGrid component gets a RowClick event, making it straightforward to turn a data row into a navigation link or an edit dialog trigger.
Two new form components—Label and DisplayName—help you build accessible forms with less boilerplate. They automatically wire up for attributes and ARIA labels based on the model metadata.
OpenAPI Binary File Responses & Dynamic Output Caching
If you expose a file download endpoint (e.g., a PDF generator), you can now annotate the action with ProducesResponseType(typeof(FileContentResult), 200, "application/pdf") and the OpenAPI generator will correctly document the binary response.
The new IOutputCachePolicyProvider interface lets you compute caching policies per request, enabling smarter CDN edge caching based on request headers or query parameters.
WSL Development Certificates
Developers who spin up ASP.NET Core inside WSL have long complained about certificate trust issues. The preview adds automatic trust propagation: a dev certificate generated inside WSL is now trusted on both the Linux side and the Windows host. No more “ERR_CERT_AUTHORITY_INVALID” pop‑ups when you hit https://localhost:5001 from a Windows browser while your server runs in WSL.
Community Pulse: Hype, Skepticism, and a Bit of AI‑Generated Angst
The .NET community is never shy about voicing opinions, and the preview sparked a lively discussion on both the official blog comments and Reddit’s r/dotnet. Here’s a quick rundown of the most common threads:
| Sentiment | What People Said |
|---|---|
| Excitement for Runtime Async | “Finally, async call stacks will be readable past the first await. This has been a pain point for years.” |
Praise for dotnet run interactivity |
“The CLI now feels like a REPL—nice to have a quick way to test on a phone without writing a launch script.” |
| Criticism of C# 15 collection expressions | “Looks like a gimmick. We already have params and collection initializers; why add another syntax?” |
| Concern about language bloat | “Are we heading toward a language that tries to solve every edge case? Feels like we’re over‑engineering.” |
| AI‑generated release notes? | “The notes read like a ChatGPT output—no real examples, just a laundry list.” |
| Positive surprise | “I love that there’s no AI‑focused marketing fluff in the runtime notes this time.” |
The mixed reaction is healthy. Runtime Async clearly hit a sweet spot, while the C# 15 syntax changes reminded us that **every new language feature needs a strong “real‑