.NET Libraries in .NET 11 Preview 4 - Release Notes

May 12, 2026 ยท View on GitHub

.NET 11 Preview 4 includes new library features and reliability improvements:

.NET Libraries updates in .NET 11:

Process gets a major API expansion

System.Diagnostics.Process picks up a substantial set of new APIs in Preview 4, covering common scenarios where developers previously had to fall back to manual Process plumbing or P/Invoke. The work spans process launch, capture, lifetime control, and handle hygiene.

Run-and-capture helpers. Process.Run, Process.RunAsync, Process.RunAndCaptureText, and Process.RunAndCaptureTextAsync give you one-shot APIs for launching a process and getting its result, without manually wiring up OutputDataReceived/ErrorDataReceived (dotnet/runtime #127210). Process.ReadAllText and Process.ReadAllBytes, plus their async variants, provide a single-call read of a child process's output (dotnet/runtime #126807, dotnet/runtime #126942). For line-oriented output, a new ProcessOutputLine struct flows through Process.ReadAllLinesAsync so callers can distinguish stdout from stderr without string parsing (dotnet/runtime #126987).

using System.Diagnostics;

// One-shot capture: stdout and stderr together, plus exit code.
ProcessTextOutput result = await Process.RunAndCaptureTextAsync(
    "git", ["status", "--porcelain"]);

Console.WriteLine(result.StandardOutput);
Console.WriteLine($"exit code: {result.ExitStatus.ExitCode}");

Fire-and-forget launches. Process.StartAndForget starts a child process when you don't intend to wait for or interact with it; the runtime detaches the handle for you (dotnet/runtime #126078). ProcessStartInfo.StartDetached goes further by detaching from the parent's session/console so the child outlives a terminal exit (dotnet/runtime #126632). On Windows, ProcessStartInfo.KillOnParentExit requests the inverse โ€” the child is terminated when the parent goes away (dotnet/runtime #126699).

SafeProcessHandle gains lifecycle methods. SafeProcessHandle.Start and a new ProcessId property let you launch and identify processes without going through Process itself (dotnet/runtime #126192). SafeProcessHandle.Kill and Signal make it possible to terminate or signal by handle (dotnet/runtime #126313), and SafeProcessHandle.WaitForExit* adds the wait/await side (dotnet/runtime #127022).

Tighter handle control. ProcessStartInfo.InheritedHandles lets you whitelist exactly which OS handles a child process inherits, instead of relying on the all-or-nothing UseShellExecute = false default (dotnet/runtime #126318). New StandardInputHandle/StandardOutputHandle/StandardErrorHandle SafeFileHandle properties on ProcessStartInfo let you supply already-open pipes or files for redirection without the framework opening new ones (dotnet/runtime #125848).

Quality of implementation. On Linux and macOS, Process no longer materializes a full ProcessInfo snapshot for ProcessName or ToString lookups, which removes a surprising amount of work from common diagnostics paths (dotnet/runtime #126449). The Unix start path also stops allocating intermediate managed strings and arrays for envp/argv, reducing GC pressure when launching many processes (dotnet/runtime #126201). System.Diagnostics.Process is now more trim-friendly: remote-machine support is initialized lazily so single-machine apps don't drag it in (dotnet/runtime #126338).

Thank you @tmds for the Linux/OSX Process optimization!

Span-based Deflate, ZLib, and GZip encoder/decoder APIs

System.IO.Compression now offers Span<byte>/ReadOnlySpan<byte> encode and decode entry points for the Deflate, ZLib, and GZip formats (dotnet/runtime #123145). The new APIs mirror the shape of BrotliEncoder/BrotliDecoder and the recently-added Zstandard primitives, so you can compress and decompress one-shot or streaming buffers without allocating a Stream. This is the missing piece for high-throughput scenarios such as protocol parsers, log shippers, and middleware that already operate on spans.

using System.Buffers;
using System.IO.Compression;

ReadOnlySpan<byte> source = File.ReadAllBytes("payload.bin");
Span<byte> destination = new byte[source.Length];

using ZLibEncoder encoder = new();
OperationStatus status = encoder.Compress(
    source, destination, out int bytesConsumed, out int bytesWritten,
    isFinalBlock: true);

Floating-point hex formatting and parsing

double, float, and Half can now be formatted and parsed in their hexadecimal IEEE-754 form (dotnet/runtime #124139). The hex form preserves every bit of the underlying value, which makes it the right choice for golden-file tests, cross-language interop with C/C++ printf("%a", ...), and any scenario where round-tripping a double through decimal text is too lossy.

using System.Globalization;

double value = Math.PI;

string hex = value.ToString("X"); // hex form, e.g., "0X1.921FB54442D18P+1"
double round = double.Parse(hex, NumberStyles.HexFloat);

Console.WriteLine(round == value); // True โ€” exact round-trip

Two complementary additions land in System.Text.Unicode (dotnet/runtime #120326). Utf16.IsValid answers "is this a well-formed UTF-16 sequence?" without scanning twice, and Utf8.IndexOfInvalidSubsequence / Utf16.IndexOfInvalidSubsequence return the position of the first ill-formed code-unit sequence (or -1 for valid input). Together they let parsers, validators, and serializers report precise errors instead of generic "encoding error" messages.

using System.Text.Unicode;

ReadOnlySpan<byte> bytes = stackalloc byte[] { 0xC3, 0x28 }; // invalid UTF-8
int badIndex = Utf8.IndexOfInvalidSubsequence(bytes); // 0

ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // ๐Ÿ‘ surrogate pair
bool ok = Utf16.IsValid(chars); // True

Thank you @lilinus for this contribution!

Rate-limiting fixes for RetryAfter, fractional permits, and ChainedRateLimiter

A cluster of System.Threading.RateLimiting fixes lands in Preview 4. FixedWindowRateLimiter now reports a RetryAfter metadata value that points at the next window boundary, so callers and middleware can honor it without guessing (dotnet/runtime #124478). TokenBucketRateLimiter.AttemptAcquire(0) no longer mishandles partial token refills (dotnet/runtime #124498). And ChainedRateLimiter now correctly forwards IdleDuration and replenishment behavior from the inner limiters instead of treating them as opaque (dotnet/runtime #126158).

If you wire RateLimiter into ASP.NET Core middleware or HTTP retry policies, the RetryAfter change in particular means clients will get a usable Retry-After header for free.

using System.Threading.RateLimiting;

var limiter = new FixedWindowRateLimiter(new()
{
    PermitLimit = 10,
    Window = TimeSpan.FromSeconds(1),
    QueueLimit = 0,
});

RateLimitLease lease = limiter.AttemptAcquire();
if (!lease.IsAcquired && lease.TryGetMetadata(MetadataName.RetryAfter, out TimeSpan retry))
{
    Console.WriteLine($"Retry after {retry}.");
}

Thank you @asbjornvad and @apoorvdarshan for these contributions!

System.Text.Json improvements

System.Text.Json continues to fill in long-standing gaps. The serializer now understands F# discriminated unions out of the box, so apps that share types between F# producers and C# consumers no longer need a custom converter for the most common shapes (dotnet/runtime #125610). Utf8JsonWriter.Reset adds overloads that accept JsonWriterOptions, so writer instances can be re-pooled with different options without allocating a new writer (dotnet/runtime #126578).

The source generator gets two correctness fixes that the team picked up from Preview 3 feedback. Generic accessor wrappers now carry through type-parameter constraints from the underlying member (dotnet/runtime #126507), and the generator now uses [UnsafeAccessor] to reach private members instead of falling back to reflection where possible (dotnet/runtime #124650). Hot-path JsonElement.GetByte/GetSByte/GetInt16/GetUInt16 reads use a shared ThrowHelper, which keeps the success path free of the exception-construction overhead (dotnet/runtime #126559).

type Shape =
    | Circle of radius: float
    | Square of side: float

let json = System.Text.Json.JsonSerializer.Serialize(Circle 1.5)
// {"$type":"Circle","radius":1.5}

Thank you @prozolic for the JsonElement and source-generator fixes!

Regex source generator and engine fixes

Several regex correctness and code-quality items land this preview. The non-backtracking engine no longer takes super-linear time on certain nested-loop patterns and produces correct results for cases that previously diverged (dotnet/runtime #125457). The regex compiler and source generator handle resumeAt correctly when a conditional appears inside a loop body (dotnet/runtime #126561). The source generator escapes U+2028/U+2029 in generated XML doc comments so the resulting compiler output stays well-formed (dotnet/runtime #126242). And the SYSLIB1045 code fixer no longer creates duplicate MyRegex names when applied across multiple partial declarations of the same class (dotnet/runtime #124698).

The regex engine also lowers MaxUnrollSize from 16 to 7, which trims generated code without measurable throughput regressions for the patterns the team measured (dotnet/runtime #126092), and the reduction stage drops a redundant Atomic wrapper from already-atomic shared prefixes (dotnet/runtime #126114).

Configuration binding and file-provider improvements

Microsoft.Extensions.Configuration adds ConfigurationIgnoreAttribute so models can opt individual properties out of binding declaratively, instead of relying on BindNonPublicProperties toggles or custom converters (dotnet/runtime #126396).

public sealed class AppOptions
{
    public string Endpoint { get; set; } = "";

    [ConfigurationIgnore]
    public string ComputedKey => Endpoint + ":default";
}

ConfigurationBinder now binds an empty array to a constructor parameter instead of throwing (dotnet/runtime #126470), and the binder source-generator suppressor only suppresses the calls it actually intercepts (dotnet/runtime #126878).

Microsoft.Extensions.FileProviders is more forgiving of unusual paths: PhysicalFilesWatcher no longer throws when its root directory does not yet exist (dotnet/runtime #126411), and InMemoryDirectoryInfo resolves .. and other relative segments consistently with the physical provider (dotnet/runtime #126320).

Built-in OpenTelemetry metrics for MemoryCache

Microsoft.Extensions.Caching.Memory.MemoryCache now emits a built-in set of OpenTelemetry-compatible metrics without an extra adapter package (dotnet/runtime #126146). The new Microsoft.Extensions.Caching.Memory.MemoryCache meter publishes four observable instruments โ€” dotnet.cache.requests (with a dotnet.cache.request.type tag distinguishing hit from miss), dotnet.cache.evictions, dotnet.cache.entries, and dotnet.cache.estimated_size โ€” and the names follow the OTel semantic-convention naming alignment that landed in the same preview (dotnet/runtime #126451), so dashboards picked up from sample OTel collectors will work without renaming.

To opt in, set MemoryCacheOptions.TrackStatistics = true. Pass an IMeterFactory to the new MemoryCache(options, loggerFactory, meterFactory) constructor for per-instance metrics; without one, the instruments are aggregated process-wide on a shared meter.

A related System.Diagnostics addition: ActivityTraceFlags.RandomTraceId (dotnet/runtime #124851) lets producers signal that the trace ID was generated with sufficient entropy, which is required by the W3C Trace Context Level 2 sampling guidance.

Thank you @rjmurillo and @Kielek for these contributions!

Discriminated-union scaffolding in System.Runtime.CompilerServices

This is a preview feature for .NET 11.

Preview 4 introduces UnionAttribute and IUnion in System.Runtime.CompilerServices (dotnet/runtime #127001). These types are the runtime side of the long-running C# discriminated-union design. They are not directly user-facing yet โ€” the C# compiler and source generators are the expected producers โ€” but they ship in the framework so libraries can begin authoring against the surface.

For the language-side design, see the C# unions proposal (champion issue dotnet/csharplang #9662).

MetadataLoadContext additions

MetadataLoadContext.GetLoadContext(Assembly) returns the load context that produced a given Assembly, mirroring the long-existing API on AssemblyLoadContext (dotnet/runtime #126926). This closes a gap for tooling that reflects over assemblies in an isolated MetadataLoadContext and needs to walk back from an Assembly reference to the context that owns it.

using System.Reflection;
using System.Reflection.Metadata;

using var mlc = new MetadataLoadContext(new PathAssemblyResolver(paths));
Assembly asm = mlc.LoadFromAssemblyPath(targetPath);

MetadataLoadContext owner = MetadataLoadContext.GetLoadContext(asm)!;
Console.WriteLine(ReferenceEquals(owner, mlc)); // True

A small follow-up removes the [NotNull] annotation on MetadataLoadContext.CoreAssembly because the property is genuinely nullable until a core assembly is loaded (dotnet/runtime #126142).

Thank you @teo-tsirpanis for these contributions!

Console respects the FORCE_COLOR environment variable

.NET console output now honors the FORCE_COLOR emerging standard alongside the existing NO_COLOR support (dotnet/runtime #124492). When FORCE_COLOR is set, Console.IsOutputRedirected no longer suppresses ANSI escape codes โ€” useful when piping dotnet run output through tee, into a CI log viewer, or through less -R.

FORCE_COLOR=1 dotnet run | tee build.log

TarReader supports GNU sparse format 1.0

System.Formats.Tar's TarReader can now read entries that use the GNU sparse format 1.0 (PAX) representation (dotnet/runtime #125283). The earlier 0.1 representation was already supported; with 1.0 in place, TarReader matches what modern tar implementations write by default for sparse files.

TLS handshake hardening and certificate-validation alerts on Linux

Two System.Net.Security items improve TLS reliability. SslStream server-side handshake bounds-checks bug fixes in TlsFrameHelper close several edge cases that could surface as IOException on malformed ClientHello records (dotnet/runtime #126352). On Linux, certificate-validation failures now surface as standard TLS alerts to the peer, matching Windows behavior (dotnet/runtime #115996). Connecting clients receive an actionable handshake error instead of a connection drop.

HTTP/2 automatic downgrade for Windows authentication

HttpClient automatically downgrades to HTTP/1.1 when a request requires Windows authentication (NTLM/Negotiate) over HTTP/2 (dotnet/runtime #123827). The HTTP/2 specification disallows the connection-bound auth schemes that NTLM and Kerberos rely on, so previously these requests would fail. With the downgrade in place, applications targeting mixed-auth environments โ€” common in enterprise intranets โ€” work without explicit HttpRequestMessage.Version overrides.

Breaking changes

  • Microsoft.Extensions.Logging moves several internal types to a new location and ships obsolete compatibility shims in their original namespaces (dotnet/runtime #127194). Code that referenced the old types (mostly logging-library authors and source generators) will compile with [Obsolete] warnings; migrate to the new locations before the shims are removed in a future release.
  • System.Security.Cryptography.Xml mitigations may reject signed/encrypted XML inputs that previously verified (dotnet/runtime #126957). Review applications that consume signed XML from third parties.
  • System.DirectoryServices.AccountManagement now escapes LDAP filter values (dotnet/runtime #126433). Applications that pre-escaped values should not be affected; applications that double-escaped will now produce different filter strings.
  • ZipArchive Update mode no longer drops data descriptors when re-saving an archive (dotnet/runtime #126447). Tools that compared archive bytes before/after a no-op update will see a different layout.

Bug fixes

Community contributors

Thank you contributors! โค๏ธ