C#idiomatic C#
librariescore/whats-new/dotnet-11

.NET releases

What's new in .NET libraries for .NET 11

Learn about the updates to the .NET libraries for .NET 11.

  • page core/whats-new/dotnet-11/libraries
  • updated 09/08/2026

This article describes new features in the .NET libraries for .NET 11. It was last updated for release candidate 1 (RC 1).

Diagnostics and process execution

Process API expansion

System.Diagnostics.Process has a substantial set of new APIs that cover common scenarios where you previously had to wire up System.Diagnostics.Process.OutputDataReceived/System.Diagnostics.Process.ErrorDataReceived events manually or use P/Invoke.

Run-and-capture helpers

New one-shot APIs let you launch a process and get its result without manual setup:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csProcessRunAndCapture

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

The full set of helpers includes:

Fire-and-forget launches

SafeProcessHandle lifecycle methods

Microsoft.Win32.SafeHandles.SafeProcessHandle gains lifecycle methods for advanced scenarios:

Tighter handle control

Suspended starts and process lookup

System.Diagnostics.Process adds finer control over starting and finding processes:

csharp
var startInfo = new ProcessStartInfo("worker.exe") { StartSuspended = true };
using var process = Process.Start(startInfo)!;

// Attach diagnostics or configure job objects while the process is suspended,
// then call Resume to start execution once your setup is complete.
AttachProfiler(process);
process.SafeHandle.Resume();

Signal processes and inspect termination status

System.Diagnostics.Process exposes process signaling and exit-status APIs directly, without requiring you to work through Microsoft.Win32.SafeHandles.SafeProcessHandle:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csProcessSignal

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Console FORCE_COLOR support

.NET console output now honors the FORCE_COLOR standard alongside the existing NO_COLOR support. When FORCE_COLOR is set, System.Console.IsOutputRedirected no longer suppresses ANSI escape codes. This is useful when you pipe dotnet run output through tee, into a CI log viewer, or through less -R:

bash
FORCE_COLOR=1 dotnet run | tee build.log

In-memory stream adapters

Four new Stream types wrap common in-memory data so you can pass them to any API that expects a Stream, without copying into a MemoryStream first:

  • ReadOnlyMemoryStream exposes a ReadOnlyMemory<byte> as a read-only stream.
  • WritableMemoryStream exposes a writable Memory<byte> as a fixed-size stream.
  • ReadOnlySequenceStream exposes a ReadOnlySequence<byte> (for example, buffers from System.IO.Pipelines) as a stream without flattening it.
  • StringStream reads a string or ReadOnlyMemory<char> as a stream using a specified encoding.
csharp
using System.IO;
using System.Text;

// Pass a string to an API that takes a Stream, with no intermediate byte[] needed.
using Stream config = new StringStream(yamlText, Encoding.UTF8);
var settings = ParseConfiguration(config);

// Expose an existing buffer as a read-only stream, for example as an HTTP request body.
ReadOnlyMemory<byte> payload = GetPayload();
using Stream body = new ReadOnlyMemoryStream(payload);
await httpClient.PostAsync(uri, new StreamContent(body));

ReadOnlySequenceStream is especially useful with System.IO.Pipelines, because it streams directly over the segments of a ReadOnlySequence<byte> instead of allocating a contiguous copy.

Text, serialization, and data handling

String and character enhancements

.NET 11 introduces significant enhancements to string and character manipulation APIs, making it easier to work with Unicode characters and runes.

Rune support in String methods

The System.String class now includes methods that accept System.Text.Rune parameters, enabling you to search, replace, and manipulate strings using Unicode scalar values directly. These new methods include:

Many of these methods include overloads that accept a System.StringComparison parameter for culture-aware comparisons.

Char.Equals with StringComparison

The System.Char struct now includes an System.Char.Equals(System.Char,System.StringComparison) method that accepts a System.StringComparison parameter, allowing you to compare characters using culture-aware or ordinal comparisons.

Rune support in TextInfo

The System.Globalization.TextInfo class now provides System.Globalization.TextInfo.ToLower(System.Text.Rune) and System.Globalization.TextInfo.ToUpper(System.Text.Rune) methods that accept System.Text.Rune parameters, enabling you to perform case conversions on individual Unicode scalar values.

StringBuilder.MoveChunks

To transfer content without copying characters, use the new static System.Text.StringBuilder.MoveChunks(System.Text.StringBuilder) method, which moves all content from a source StringBuilder to a new StringBuilder. After the call, the source StringBuilder has a length of 0:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csStringBuilderMoveChunks

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Base64 encoding improvements

.NET 11 adds new APIs and overloads to the existing System.Buffers.Text.Base64 type, providing comprehensive support for Base64 encoding and decoding. These additions offer improved performance and flexibility compared to existing methods.

New Base64 APIs

The new APIs support encoding and decoding operations with various input and output formats:

These methods provide both high-level convenience methods (that allocate and return arrays or strings) and low-level span-based methods (for zero-allocation scenarios).

System.Text.Unicode has two new complementary features. System.Text.Unicode.Utf16.IsValid(System.ReadOnlySpan{System.Char}) answers whether a sequence is well-formed UTF-16 without scanning twice, and System.Text.Unicode.Utf8.IndexOfInvalidSubsequence(System.ReadOnlySpan{System.Byte}) / System.Text.Unicode.Utf16.IndexOfInvalidSubsequence(System.ReadOnlySpan{System.Char}) return the position of the first ill-formed code-unit sequence (or -1 for valid input). Together, these methods let parsers, validators, and serializers report precise errors instead of generic encoding-error messages.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csUtfValidation

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

System.Text.Json improvements

Generic type info retrieval

A common pattern when working with System.Text.Json type metadata is to retrieve a System.Text.Json.Serialization.Metadata.JsonTypeInfo from System.Text.Json.JsonSerializerOptions. Previously, you had to manually downcast from the non-generic System.Text.Json.JsonSerializerOptions.GetTypeInfo(System.Type) method. New generic [System.Text.Json.JsonSerializerOptions.GetTypeInfo](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.gettypeinfo--1) and [System.Text.Json.JsonSerializerOptions.TryGetTypeInfo(System.Text.Json.Serialization.Metadata.JsonTypeInfo{`}@)](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.trygettypeinfo--1) methods return strongly typed metadata directly, eliminating the cast.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csJsonTypeInfoGeneric

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

This is particularly useful when working with source generation, NativeAOT, and polymorphic serialization scenarios where type metadata access is common.

Naming and ignore defaults

The naming and ignore options available in System.Text.Json now include:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csJsonNamingIgnore

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

F# discriminated union support

The serializer now understands F# discriminated unions out of the box. Apps that share types between F# producers and C# consumers no longer need a custom converter for the most common shapes:

3

Utf8JsonWriter.Reset with options

System.Text.Json.Utf8JsonWriter.Reset now accepts a System.Text.Json.JsonWriterOptions parameter, so writer instances can be repooled with different options without allocating a new writer:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csUtf8JsonWriterReset

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

SerializeAsyncEnumerable improvements

System.Text.Json.JsonSerializer.SerializeAsyncEnumerable gains two new capabilities:

  • PipeWriter target: For pipeline-based I/O scenarios, new overloads accept a System.IO.Pipelines.PipeWriter as the output destination, making it easy to integrate JSON streaming directly.
  • topLevelValues parameter: For NDJSON output, set this parameter to true to write each item as a top-level JSON value separated by newlines instead of wrapping all items in a JSON array. Both Stream and PipeWriter overloads support the parameter.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csJsonSerializeAsyncEnumerablePipe

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Numeric type and binary-schema support

System.Text.Json includes built-in converters for System.Numerics.BFloat16, System.Numerics.Decimal32, System.Numerics.Decimal64, and System.Numerics.Decimal128. These converters work with both the reflection-based serializer and the source generator, including named floating-point literals when enabled through System.Text.Json.Serialization.JsonNumberHandling.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csJsonNumericTypes

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

System.Text.Json.Schema.JsonSchemaExporter also identifies the base64 representation used for byte[], System.Memory, and System.ReadOnlyMemory. For byte[], the exported schema now includes a contentEncoding keyword:

4

The Memory<byte> and ReadOnlyMemory<byte> schemas remain non-nullable ("type": "string") and also include contentEncoding.

C# union type serialization

System.Text.Json can now serialize and deserialize C# union types. The serializer recognizes a union through the new JsonTypeInfoKind.Union contract kind, reads and writes the active case, and supports both the reflection-based serializer and the source generator. When you serialize a union, System.Text.Json writes the value of whichever case is active, so a union of int and string round-trips cleanly:

5

6

The new JsonUnionAttribute and JsonUnionCaseInfo APIs, along with type-classifier APIs (JsonTypeClassifier and JsonSerializerOptions.TypeClassifiers), let you customize how cases are discovered and named. Union types are a C# language preview feature. For more information, see What's new in C# 15.

For a union with object-shaped cases, the built-in JsonUnionTypeStructuralClassifier selects the active case from its distinguishing property names, so you don't need to author a custom classifier:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csJsonUnionStructuralClassifier

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Closed-hierarchy polymorphism inference

System.Text.Json.JsonSerializerOptions adds System.Text.Json.JsonSerializerOptions.InferClosedTypePolymorphism so the serializer can infer polymorphic metadata for C# closed hierarchies without requiring explicit System.Text.Json.Serialization.JsonDerivedTypeAttribute annotations on each base type. Explicit registrations still take precedence.

You can also opt a single closed hierarchy in to this behavior with System.Text.Json.Serialization.JsonPolymorphicAttribute.InferClosedTypePolymorphism, without changing the application-wide JsonSerializerOptions setting:

7

Regular expression improvements

AnyNewLine option

A new System.Text.RegularExpressions.RegexOptions.AnyNewLine flag makes ^, $, and . treat the full set of Unicode newline characters as line terminatorsβ€”not just \n. This helps when parsing text that mixes Windows (\r\n), Unix (\n), and Unicode-specific (\u0085, \u2028, \u2029) line endings.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csRegexAnyNewLine

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Regex engine and source generator fixes

.NET 11 includes several regex correctness and code-quality fixes:

  • The non-backtracking engine no longer takes super-linear time on certain nested-loop patterns and produces correct results for cases that previously diverged.
  • The regex compiler and source generator handle resumeAt correctly when a conditional appears inside a loop body.
  • The SYSLIB1045 code fixer no longer creates duplicate class names when applied across multiple partial declarations of the same class.

Compression and archive formats

Compression enhancements

.NET 11 includes several improvements to compression APIs.

ZIP archive entry access modes

The System.IO.Compression.ZipArchiveEntry class now supports opening entries with specific file access modes through new overloads: System.IO.Compression.ZipArchiveEntry.Open(System.IO.FileAccess) and System.IO.Compression.ZipArchiveEntry.OpenAsync(System.IO.FileAccess,System.Threading.CancellationToken). These overloads accept a System.IO.FileAccess parameter and allow you to open ZIP entries for read, write, or read-write access.

Additionally, a new System.IO.Compression.ZipArchiveEntry.CompressionMethod property exposes the compression method used for an entry through the System.IO.Compression.ZipCompressionMethod enum, which includes values for System.IO.Compression.ZipCompressionMethod.Stored, System.IO.Compression.ZipCompressionMethod.Deflate, and System.IO.Compression.ZipCompressionMethod.Deflate64.

ZIP CRC32 validation

System.IO.Compression.ZipArchive validates the CRC32 checksum when reading ZIP entries. Corrupted or truncated archives that previously passed without error now throw System.IO.InvalidDataException, helping you detect data integrity issues early.

ZIP archive password support

System.IO.Compression.ZipArchiveEntry now supports password-protected entries for read and write operations. Use the new password-aware System.IO.Compression.ZipArchiveEntry.Open(System.ReadOnlySpan{System.Char}) and System.IO.Compression.ZipArchiveEntry.OpenAsync(System.ReadOnlySpan{System.Char},System.Threading.CancellationToken) overloads, and use password plus System.IO.Compression.ZipEncryptionMethod when you create encrypted entries.

DeflateStream and GZipStream behavior change

Starting in .NET 11, System.IO.Compression.DeflateStream and System.IO.Compression.GZipStream always write format headers and footers to the output stream, even when no data is written. This ensures the output is a valid compressed stream according to the Deflate and GZip specifications.

Previously, these streams didn't produce any output if no data was written, resulting in an empty output stream. This change ensures compatibility with tools that expect properly formatted compressed streams.

For more information, see DeflateStream and GZipStream write headers and footers for empty payload.

Span-based Deflate, ZLib, and GZip APIs

System.IO.Compression now offers Span<byte>/ReadOnlySpan<byte> encode and decode entry points for the Deflate, ZLib, and GZip formats. The new APIs, on types such as System.IO.Compression.DeflateEncoder, System.IO.Compression.ZLibEncoder, and System.IO.Compression.GZipEncoder, mirror the shape of System.IO.Compression.BrotliEncoder/System.IO.Compression.BrotliDecoder and the Zstandard primitives. You can compress and decompress buffers without allocating a Stream. This is useful for high-throughput scenarios such as protocol parsers, log shippers, and middleware that already operate on spans.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csZLibEncoderSpan

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

The streamless DeflateEncoder, DeflateDecoder, ZLibEncoder, ZLibDecoder, GZipEncoder, and GZipDecoder types also gain a Reset() method. Resetting returns an instance to its initial state so you can process another independent payload without allocating a replacement encoder or decoder.

Zstandard compression

The Zstandard compression APIs, for example, System.IO.Compression.ZstandardStream and System.IO.Compression.ZstandardEncoder, are now part of the System.IO.Compression namespace, alongside DeflateStream, GZipStream, and BrotliStream. The API surface is otherwise unchanged.

Tar archive format selection

New overloads on System.Formats.Tar.TarFile.CreateFromDirectory and System.Formats.Tar.TarFile.CreateFromDirectoryAsync accept a System.Formats.Tar.TarEntryFormat parameter, giving you direct control over the archive format. Previously, CreateFromDirectory always produced Pax archives. The new overloads support all four tar formatsβ€”Pax, Ustar, GNU, and V7β€”for compatibility with specific tools and environments.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csTarArchiveFormat

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

TarReader can now also read entries that use the GNU sparse format 1.0 (PAX) representation. The earlier 0.1 representation was already supported. With 1.0 support in place, TarReader matches what modern tar implementations write by default for sparse files.

Collections, numerics, and low-level I/O

LINQ join improvements

LINQ adds join enhancements, including a new FullJoin operation and tuple-returning overloads for the existing Join and GroupJoin methods. These APIs are available on System.Linq.Enumerable, System.Linq.Queryable, and System.Linq.AsyncEnumerable.

LeftJoin, RightJoin, and FullJoin

Because the three outer-join operations return tuples directly, you can work with them easily using pattern matching and deconstruction:

  • System.Linq.Enumerable.LeftJoin β€” Returns all elements from the left (outer) sequence. When there's no matching element in the right (inner) sequence, the inner element is default (typically null for reference types).
  • System.Linq.Enumerable.RightJoin β€” Returns all elements from the right (inner) sequence. When there's no matching element in the left (outer) sequence, the outer element is default (typically null for reference types).
  • System.Linq.Enumerable.FullJoin β€” Returns all elements from both sequences, using default on either side when there's no match.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csLinqJoins

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

IEEE 754 decimal floating-point types

System.Numerics adds System.Numerics.Decimal32, System.Numerics.Decimal64, and System.Numerics.Decimal128, which implement IEEE 754-2019 decimal floating-point semantics. These types support generic math, infinities, and NaN values, and can help when you need IEEE decimal behavior instead of System.Decimal.

Correctly rounded decimal and BigInteger conversions

Conversions between System.Decimal and binary floating-point types, and conversions from System.Numerics.BigInteger to binary floating-point types, now round the exact source value once to the nearest representable destination value. The previous conversions could lose significant digits or round through an intermediate value, so binaries rebuilt with a .NET 11 SDK can produce different results than they did on earlier versions. There's no compatibility switch to restore the previous conversion algorithms.

For example, converting the double literal 1.23 to decimal now preserves the exact binary floating-point value rather than producing 1.23. If a value is meant to be decimal, use a decimal literal such as 1.23m instead of converting a double literal. For complete guidance, including previous and new behavior for each conversion direction, see Decimal and BigInteger floating-point conversions are correctly rounded.

Partial numeric parsing

System.Numerics.INumberBase.TryParsePartial adds partial parsing overloads that report consumed input. Use this API to parse delimited formats, such as CSV, so you don't copy substrings.

Generic Complex<T>

System.Numerics.Complex is now available, so complex arithmetic can use several floating-point types, including float, Half, BFloat16, and the new decimal floating-point types.

Tuple-returning Join and GroupJoin overloads

New overloads of System.Linq.Enumerable.Join and System.Linq.Enumerable.GroupJoin return tuples directly without requiring a result selector, and accept an optional System.Collections.Generic.IEqualityComparer parameter. The optional comparer overload is also available on the FullJoin, LeftJoin, and RightJoin operations.

8

BFloat16 support in BitConverter

The System.BitConverter class now includes methods for converting between System.Numerics.BFloat16 values and byte arrays or bit representations. These new methods include:

BFloat16 (Brain Floating Point) is a 16-bit floating-point format that's commonly used in machine learning and scientific computing.

Floating-point hex formatting and parsing

double, float, and Half can now be formatted and parsed in their hexadecimal IEEE-754 form. The hex form preserves every bit of the underlying value, making 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.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csFloatingPointHex

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Numerics improvements

System.Numerics.Matrix4x4.GetDeterminant now uses an SSE-vectorized implementation, improving performance by approximately 15%.

Random generic methods

System.Random gains two new generic methods that work with any numeric type implementing the appropriate generic math interfaces:

  • System.Random.NextInteger β€” Generates a random integer of type T where T implements IBinaryInteger<T> and IMinMaxValue<T>. Overloads accept an upper bound or both lower and upper bounds.
  • System.Random.NextBinaryFloat β€” Generates a random floating-point value of type T where T implements IBinaryFloatingPointIeee754<T>.

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csRandomGeneric

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Cross-lane vector operations

Vector64<T>, Vector128<T>, Vector256<T>, Vector512<T>, and Vector<T> gain a set of lane construction and composition methods that previously required hand-written shuffles. The new methods fall into a few families:

  • Patterned construction: CreateGeometricSequence, CreateAlternatingSequence, and CreateHarmonicSequence build a vector from a starting value and a rule.
  • Interleave and de-interleave: Zip, ZipLower/ZipUpper, Unzip, UnzipEven/UnzipOdd.
  • Rearrange: The Concat family (ConcatLowerLower, ConcatLowerUpper, ConcatUpperLower, ConcatUpperUpper) and Reverse.

9

These building blocks are useful for image processing, audio digital signal processing (DSP), and other SIMD-intensive workloads that need fine-grained control over vector element layout, without falling back to platform-specific intrinsics.

Low-level I/O improvements

SafeFileHandle pipe support

Microsoft.Win32.SafeHandles.SafeFileHandle gains two new members:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csSafeFileHandlePipe

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

RandomAccess pipe support

System.IO.RandomAccess.Read and System.IO.RandomAccess.Write now work with non-seekable handles such as pipes, in addition to regular file handles.

On Windows, Process now uses overlapped I/O for redirected stdout/stderr, which reduces thread-pool blocking in process-heavy applications.

Collections improvements

BitArray span constructors

System.Collections.BitArray now has constructors that accept System.ReadOnlySpan: ReadOnlySpan<bool>, ReadOnlySpan<byte>, and ReadOnlySpan<int>. You can construct a bit array directly from a slice or stack-allocated data without first allocating an array:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csBitArraySpanConstructor

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

The span-based constructors avoid the intermediate array allocation that new BitArray(span.ToArray()) requires.

BitArray.PopCount

The System.Collections.BitArray class now includes a System.Collections.BitArray.PopCount method that returns the number of bits set to true in the array. This provides an efficient way to count set bits without manually iterating through the array.

IReadOnlySet support in JSON serialization

The System.Text.Json.Serialization.Metadata.JsonMetadataServices class now includes a System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateIReadOnlySetInfo method, enabling JSON serialization support for System.Collections.Generic.IReadOnlySet collections.

EqualityComparer<T>.Create

To create an equality comparer from a key selector function, use the new System.Collections.Generic.EqualityComparer.Create factory method. You can pass an optional System.Collections.Generic.IEqualityComparer for the key type itself:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csEqualityComparerCreate

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Extensions and developer platform

Discriminated-union scaffolding

.NET 11 introduces System.Runtime.CompilerServices.UnionAttribute and System.Runtime.CompilerServices.IUnion in System.Runtime.CompilerServices. These types are the runtime side of the C# discriminated-union design. They aren't directly user-facing yetβ€”the C# compiler and source generators are the expected producersβ€”but they ship in the framework so libraries can author against the surface now.

For the language-side design, see the discriminated unions working group overview.

MetadataLoadContext additions

System.Reflection.MetadataLoadContext.GetLoadContext(System.Reflection.Assembly) returns the load context that produced a given Assembly, mirroring the long-existing API on System.Runtime.Loader.AssemblyLoadContext. 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:

10

URI data scheme constant

A new System.Uri.UriSchemeData constant has been added, representing the data: URI scheme. This constant provides a standardized way to reference data URIs.

Reflection improvements

Type.GetNullableUnderlyingType

System.Type gains a new virtual method, System.Type.GetNullableUnderlyingType, that returns the underlying value type for a Nullable<T> type, or null for any non-nullable type. Implementations are provided on System.Type, System.Reflection.Emit.TypeBuilder, System.Reflection.Emit.EnumBuilder, System.Reflection.Emit.GenericTypeParameterBuilder, and System.Reflection.TypeDelegator:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csNullableUnderlyingType

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

ConstructorInfo.GetGenericArguments

System.Reflection.ConstructorInfo.GetGenericArguments now has an override, providing a consistent way to retrieve generic type arguments for constructor definitions, matching the behavior already available on other MethodBase subclasses.

Assembly.Location override for AssemblyLoadContext

System.Runtime.Loader.AssemblyLoadContext.SetAssemblyLocationOverride(System.Func{System.Reflection.Assembly,System.String,System.String}) is a set-once callback that can override the location reported by System.Reflection.Assembly.Location. This API helps hosts that stage assemblies in temporary paths or virtualized locations report stable, meaningful locations to diagnostics and resource-loading code.

Reduced contention in System.IO.Pipelines

System.IO.Pipelines reduces reader and writer lock contention under high fan-in workloads. Internal changes include lower lock scope, local queue continuation scheduling, and pool updates that improve cache locality.

StringSyntax attribute enhancements

The System.Diagnostics.CodeAnalysis.StringSyntaxAttribute class now includes constants for common programming languages:

These constants can be used with the StringSyntax attribute to provide better tooling support for string literals containing code in these languages.

Caching, configuration, and logging

Configuration binding

Microsoft.Extensions.Configuration adds Microsoft.Extensions.Configuration.ConfigurationIgnoreAttribute, so models can opt individual properties out of binding declaratively without relying on BindNonPublicProperties toggles or custom converters:

11

ConfigurationBinder now also binds an empty array to a constructor parameter instead of throwing.

PhysicalFilesWatcher no longer throws when its root directory doesn't yet exist, and InMemoryDirectoryInfo resolves .. and other relative segments consistently with the physical provider.

MemoryCache OpenTelemetry metrics

Microsoft.Extensions.Caching.Memory.MemoryCache now emits a built-in set of OpenTelemetry (OTel)-compatible metrics without an extra adapter package. To opt in, set Microsoft.Extensions.Caching.Memory.MemoryCacheOptions.TrackStatistics to true:

12

The new Microsoft.Extensions.Caching.Memory.MemoryCache meter publishes four observable instruments:

  • dotnet.cache.requests (with a dotnet.cache.request.type tag that distinguishes hit from miss)
  • dotnet.cache.evictions
  • dotnet.cache.entries
  • dotnet.cache.estimated_size

Pass an System.Diagnostics.Metrics.IMeterFactory to the new Microsoft.Extensions.Caching.Memory.MemoryCache.%23ctor(Microsoft.Extensions.Options.IOptions{Microsoft.Extensions.Caching.Memory.MemoryCacheOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Diagnostics.Metrics.IMeterFactory) constructor overload for per-instance metrics. Without one, the instruments are aggregated process-wide on a shared meter.

Options builder validation improvements

Microsoft.Extensions.Options.OptionsBuilder gains a new generic Microsoft.Extensions.Options.OptionsBuilder.Validate overload that accepts a type parameter instead of a factory delegate. The type must implement Microsoft.Extensions.Options.IValidateOptions and be registered in the dependency-injection container. This aligns options validation with the standard DI pattern:

13

Asynchronous ChangeToken.OnChange

Microsoft.Extensions.Primitives.ChangeToken.OnChange now has Func<Task> overloads, so you can run asynchronous callbacks without using async void. Re-registration now happens after the returned task completes.

Options-aware HybridCache factories

Microsoft.Extensions.Caching.Hybrid.HybridCache.GetOrCreateAsync now has overloads that provide a mutable factory context. You can set entry-level options, such as expiration and local cache size, from the value-producing callback.

Asynchronous validation with DataAnnotations

System.ComponentModel.DataAnnotations now supports asynchronous validation. A validation rule that needs to do I/Oβ€”a database lookup or a remote API callβ€”can now run without blocking a thread. There are three new ways to express an async rule:

  • Derive from AsyncValidationAttribute and override its IsValidAsync method.
  • Implement IAsyncValidatableObject on the model.
  • Call the new Validator.ValidateObjectAsync, TryValidateObjectAsync, ValidatePropertyAsync, and ValidateValueAsync methods.

14

Microsoft.Extensions.Options gains matching support: options can be validated asynchronously, including at startup through the new IAsyncStartupValidator. This lets an app fail fast when an option that requires a network check is misconfigured. IStartupValidator is now obsolete; implement IAsyncStartupValidator instead.

Source-generated options validators provide a synchronous IValidateOptions<TOptions>.Validate implementation in addition to asynchronous validation, so existing synchronous validation call sites keep working alongside validators that need to await I/O:

15

Activity tracing configuration

A new tracing configuration API in Microsoft.Extensions.Diagnostics lets you turn Activity tracing on and off with rules instead of wiring up ActivityListener instances manually. Call AddTracing and describe which activity sources, operations, and listeners to enable or disable. Rules can be driven from configuration, so tracing can be tuned without a redeploy.

16

The same release adds ActivitySourceFactory and unseals ActivitySource, which together support factory-driven creation and refreshable listeners.

Generic source-generated logging methods

The logging source generator now supports generic methods decorated with Microsoft.Extensions.Logging.LoggerMessageAttribute, primarily to avoid boxing. Generic type parameters can use standard constraints, such as class, struct, unmanaged, interfaces, base types, and new(). The allows ref struct anti-constraint isn't supported and produces SYSLIB1011.

17

Improved reliability for configuration file change detection

The Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher type, which configuration providers use to detect configuration file changes, includes several reliability improvements.

It now handles relative paths, missing directories, and file systems that don't support file watching more reliably. It also avoids watching subdirectories when it doesn't need to.

Cryptography

X25519 Diffie-Hellman key exchange

The new System.Security.Cryptography.X25519DiffieHellman abstract class provides a clean API for the X25519 elliptic-curve Diffie-Hellman algorithm, which is widely used in TLS 1.3, SSH, and Signal-protocol implementations. Platform-specific implementations are provided through System.Security.Cryptography.X25519DiffieHellmanCng (Windows) and System.Security.Cryptography.X25519DiffieHellmanOpenSsl (Linux and macOS), but the portable GenerateKey() factory method picks the right one automatically:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csX25519KeyExchange

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

The class supports the full key lifecycle: key generation, PKCS#8 and SubjectPublicKeyInfo import/export, PEM serialization, and raw private/public key access.

CryptographicOperations.FixedTimeEquals overload

For secret-comparison scenarios where the expected value is a single known byte, use the new System.Security.Cryptography.CryptographicOperations.FixedTimeEquals overload that compares a System.ReadOnlySpan with a single byte value in constant time:

18

AES Key Wrap

System.Security.Cryptography.Aes now supports the AES Key Wrap algorithms used by JOSE and other libraries to encrypt cryptographic keys:

Both sets of APIs provide array-returning and span-based overloads:

sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csAesKeyWrap

csharp
using System.Buffers;
using System.Collections;
using System.Diagnostics;
using System.Formats.Tar;
using System.Globalization;
using System.IO.Compression;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Microsoft.Win32.SafeHandles;

public static class LibrariesExamples
{
    static async Task ProcessRunAndCaptureExample()
    {
        // <ProcessRunAndCapture>
        // 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}");
        // </ProcessRunAndCapture>
    }

    static async Task ProcessSignalExample()
    {
        // <ProcessSignal>
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            using Process process = Process.Start("sleep", "30")!;
            process.Signal(PosixSignal.SIGTERM);
            ProcessExitStatus status = await process.WaitForExitStatusAsync();
            Console.WriteLine(status);
        }
        // </ProcessSignal>
    }

    static void ZLibEncoderSpanExample()
    {
        // <ZLibEncoderSpan>
        ReadOnlySpan<byte> source = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello"
        byte[] buffer = new byte[source.Length + 32];
        Span<byte> destination = buffer;

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

        Console.WriteLine($"Compressed {bytesConsumed} bytes into {bytesWritten} bytes. Status: {status}");
        // </ZLibEncoderSpan>
    }

    static void FloatingPointHexExample()
    {
        // <FloatingPointHex>
        double value = Math.PI;

        // Format as hexadecimal IEEE-754: preserves all bits exactly
        string hex = value.ToString("X"); // e.g., "0X1.921FB54442D18P+1"
        double roundTripped = double.Parse(hex, NumberStyles.HexFloat);

        Console.WriteLine(roundTripped == value); // True β€” exact round-trip
        // </FloatingPointHex>
    }

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

        ReadOnlySpan<char> chars = "valid \uD83D\uDC4D end"; // valid UTF-16 (πŸ‘ emoji)
        bool ok = Utf16.IsValid(chars); // true
        // </UtfValidation>
    }

    static void Utf8JsonWriterResetExample()
    {
        // <Utf8JsonWriterReset>
        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
        writer.WriteStartObject();
        writer.WriteString("name", "example");
        writer.WriteEndObject();
        writer.Flush();

        // Reset with different options for next use β€” no new allocation needed
        stream.SetLength(0);
        writer.Reset(stream, new JsonWriterOptions { Indented = false });
        // </Utf8JsonWriterReset>
    }

    static void JsonTypeInfoExample()
    {
        // <JsonTypeInfoGeneric>
        JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
        options.MakeReadOnly();

        // Before: manual downcast required
        JsonTypeInfo<MyRecord> info1 = (JsonTypeInfo<MyRecord>)options.GetTypeInfo(typeof(MyRecord));

        // After: generic method returns the right type directly
        JsonTypeInfo<MyRecord> info2 = options.GetTypeInfo<MyRecord>();

        // TryGetTypeInfo variant for cases where the type may not be registered
        if (options.TryGetTypeInfo<MyRecord>(out JsonTypeInfo<MyRecord>? typeInfo))
        {
            // Use typeInfo
            _ = typeInfo;
        }
        // </JsonTypeInfoGeneric>
    }

    static void JsonNamingIgnoreExample()
    {
        // <JsonNamingIgnore>
        // Type-level JsonIgnore: all members use WhenWritingNull by default
        // Per-member JsonNamingPolicy: EventName uses camelCase even though the
        // serializer options use PascalCase
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.PascalCase
        };

        var data = new EventData { EventName = "Launch", Notes = null };
        string json = JsonSerializer.Serialize(data, options);
        Console.WriteLine(json);
        // {"eventName":"Launch"}  -- Notes omitted (null), EventName camel-cased
        // </JsonNamingIgnore>
    }

    static async Task TarArchiveFormatExample()
    {
        // <TarArchiveFormat>
        // Create a GNU format tar archive for Linux compatibility
        TarFile.CreateFromDirectory("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Gnu);

        // Create a Ustar format archive for broader compatibility
        using Stream outputStream = File.OpenWrite("/dest/ustar.tar");
        TarFile.CreateFromDirectory("/source/dir", outputStream,
            includeBaseDirectory: false, format: TarEntryFormat.Ustar);

        // Async version
        CancellationToken cancellationToken = CancellationToken.None;
        await TarFile.CreateFromDirectoryAsync("/source/dir", "/dest/archive.tar",
            includeBaseDirectory: true, format: TarEntryFormat.Pax,
            cancellationToken: cancellationToken);
        // </TarArchiveFormat>
    }

    static void SafeFileHandlePipeExample()
    {
        // <SafeFileHandlePipe>
        SafeFileHandle.CreateAnonymousPipe(
            out SafeFileHandle readEnd,
            out SafeFileHandle writeEnd,
            asyncRead: true,
            asyncWrite: false);

        using (readEnd)
        using (writeEnd)
        {
            // SafeFileHandle.Type reports the kind of OS object the handle refers to
            Console.WriteLine(readEnd.Type);   // Pipe
            Console.WriteLine(writeEnd.Type);  // Pipe
        }
        // </SafeFileHandlePipe>
    }

    static void RegexAnyNewLineExample()
    {
        // <RegexAnyNewLine>
        string text = "line1\r\nline2\u0085line3\u2028line4";

        // RegexOptions.AnyNewLine makes ^, $, and . treat all Unicode newline
        // sequences as line terminators, not just \n.
        MatchCollection matches = Regex.Matches(
            text,
            @"^line\d$",
            RegexOptions.Multiline | RegexOptions.AnyNewLine);

        Console.WriteLine(matches.Count); // 4
        // </RegexAnyNewLine>
    }

    public static void LinqJoinsExample()
    {
        // <LinqJoins>
        var products = new List<(int Id, string Name, string? Category)>
        {
            (1, "Laptop", "Electronics"),
            (2, "Mouse", "Electronics"),
            (3, "Orphan", null), // No matching category
        };
        var categories = new List<(string Name, string Description)>
        {
            ("Electronics", "Electronic devices"),
            ("Furniture", "Office furniture"), // No matching product
        };

        // LeftJoin: all products, matched categories (null if none)
        var leftJoined = products.LeftJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in leftJoined)
            Console.WriteLine($"{product.Name}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)

        // FullJoin: all products and categories, paired where they match
        var fullJoined = products.FullJoin(
            categories,
            p => p.Category,
            c => c.Name);

        foreach (var (product, category) in fullJoined)
            Console.WriteLine(
                $"{product.Name ?? "(none)"}: {category.Description ?? "(none)"}");
        // Laptop: Electronic devices
        // Mouse: Electronic devices
        // Orphan: (none)
        // (none): Office furniture
        // </LinqJoins>
    }

    static void BitArraySpanConstructorExample()
    {
        // <BitArraySpanConstructor>
        Span<byte> bytes = stackalloc byte[] { 0b_0000_0011, 0b_1000_0000 };
        var bits = new BitArray(bytes);

        Console.WriteLine(bits[0]);  // True
        Console.WriteLine(bits[1]);  // True
        Console.WriteLine(bits[15]); // True
        // </BitArraySpanConstructor>
    }

    static void EqualityComparerCreateExample()
    {
        // <EqualityComparerCreate>

        // Create an equality comparer based on a key selector
        var byName = EqualityComparer<(string Name, int Age)>.Create(p => p.Name);

        var people = new HashSet<(string Name, int Age)>(byName)
        {
            ("Alice", 30),
            ("Bob", 25),
            ("Alice", 40), // Duplicate by name β€” not added
        };
        Console.WriteLine(people.Count); // 2
        // </EqualityComparerCreate>
    }

    static void RandomGenericExample()
    {
        // <RandomGeneric>
        // Generate a random integer of any binary integer type
        int i = Random.Shared.NextInteger<int>();
        long l = Random.Shared.NextInteger<long>(0L, 100L);
        byte b = Random.Shared.NextInteger<byte>(maxValue: (byte)10);

        // Generate a random floating-point value of any IEEE-754 type
        float f = Random.Shared.NextBinaryFloat<float>();
        double d = Random.Shared.NextBinaryFloat<double>();
        Half h = Random.Shared.NextBinaryFloat<Half>();

        Console.WriteLine($"int={i}, long={l}, byte={b}");
        Console.WriteLine($"float={f}, double={d}, Half={h}");
        // </RandomGeneric>
    }

    static void StringBuilderMoveChunksExample()
    {
        // <StringBuilderMoveChunks>
        var source = new StringBuilder("Hello, ");
        source.Append("World!");

        // MoveChunks transfers all content from source to a new StringBuilder.
        // After the call, source contains no characters.
        StringBuilder dest = StringBuilder.MoveChunks(source);
        Console.WriteLine(dest);          // Hello, World!
        Console.WriteLine(source.Length); // 0
        // </StringBuilderMoveChunks>
    }

    static async Task JsonSerializeAsyncEnumerablePipeExample()
    {
        // <JsonSerializeAsyncEnumerablePipe>
        static async IAsyncEnumerable<int> GenerateNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                yield return i;
                await Task.Yield();
            }
        }

        var pipe = new Pipe();

        // Write a JSON array: [0,1,2,3,4]
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers());

        // Write NDJSON (one value per line): 0\n1\n2\n3\n4\n
        await JsonSerializer.SerializeAsyncEnumerable(
            pipe.Writer,
            GenerateNumbers(),
            topLevelValues: true);
        // </JsonSerializeAsyncEnumerablePipe>
    }

    static void JsonNumericTypesExample()
    {
        // <JsonNumericTypes>
        var measurement = new Measurement((Decimal64)1.229m);
        string json = JsonSerializer.Serialize(measurement);
        Console.WriteLine(json); // {"Voltage":1.229}

        Measurement? roundTripped = JsonSerializer.Deserialize<Measurement>(json);
        Console.WriteLine(roundTripped); // Measurement { Voltage = 1.229 }
        // </JsonNumericTypes>
    }

    static void JsonUnionStructuralClassifierExample()
    {
        // <JsonUnionStructuralClassifier>
        PetUnion? pet = JsonSerializer.Deserialize<PetUnion>(
            """{"Name":"Misty","Lives":9}""",
            PetJsonContext.Default.PetUnion);

        Console.WriteLine(pet?.Value is Cat); // True
        // </JsonUnionStructuralClassifier>
    }

    static void X25519KeyExchangeExample()
    {
        // <X25519KeyExchange>
        // Generate key pairs for Alice and Bob
        using X25519DiffieHellman alice = X25519DiffieHellman.GenerateKey();
        using X25519DiffieHellman bob = X25519DiffieHellman.GenerateKey();

        // Each party derives the shared secret using the other's public key
        byte[] aliceShared = alice.DeriveRawSecretAgreement(bob);
        byte[] bobShared = bob.DeriveRawSecretAgreement(alice);

        // Both parties arrive at the same secret
        Console.WriteLine(aliceShared.SequenceEqual(bobShared)); // True
        // </X25519KeyExchange>
    }

    static void AesKeyWrapExample()
    {
        // <AesKeyWrap>
        using Aes aes = Aes.Create();
        aes.Key = RandomNumberGenerator.GetBytes(32); // AES-256 key-encryption key

        byte[] keyToWrap = RandomNumberGenerator.GetBytes(24); // Multiple of 8 bytes

        // Unpadded AES Key Wrap (RFC 3394)
        byte[] wrapped = aes.EncryptKeyWrap(keyToWrap);
        byte[] unwrapped = aes.DecryptKeyWrap(wrapped);
        Console.WriteLine(keyToWrap.SequenceEqual(unwrapped)); // True

        // Padded AES-KWP (RFC 5649) accepts a plaintext of any length
        byte[] arbitraryLengthKey = RandomNumberGenerator.GetBytes(19);
        byte[] wrappedPadded = aes.EncryptKeyWrapPadded(arbitraryLengthKey);
        byte[] unwrappedPadded = aes.DecryptKeyWrapPadded(wrappedPadded);
        Console.WriteLine(arbitraryLengthKey.SequenceEqual(unwrappedPadded)); // True
        // </AesKeyWrap>
    }

    static void NullableUnderlyingTypeExample()
    {
        // <NullableUnderlyingType>
        Type nullableIntType = typeof(int?);
        Type? underlying = nullableIntType.GetNullableUnderlyingType();
        Console.WriteLine(underlying); // System.Int32

        Type nonNullable = typeof(int);
        Console.WriteLine(nonNullable.GetNullableUnderlyingType() is null); // True
        // </NullableUnderlyingType>
    }
}

record MyRecord(string Name, int Value);

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
sealed class EventData
{
    [JsonNamingPolicy(JsonKnownNamingPolicy.CamelCase)]
    public string EventName { get; set; } = "";

    public string? Notes { get; set; }
}

readonly record struct Measurement(Decimal64 Voltage);

[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union PetUnion(Dog, Cat);

public sealed record Dog(string Name, string Breed);

public sealed record Cat(string Name, int Lives);

[JsonSerializable(typeof(PetUnion))]
internal partial class PetJsonContext : JsonSerializerContext;

Faster authenticated encryption on Apple platforms

System.Security.Cryptography.AesGcm and System.Security.Cryptography.ChaCha20Poly1305 avoid passing empty associated data to CryptoKit on Apple platforms, letting the native implementation use its empty-AAD fast path. On Apple platforms version 26 and later, encryption also skips a copy that was previously required for older Foundation implementations.

Networking and transport security

TLS handshake hardening

Two System.Net.Security items improve TLS (Transport Layer Security) reliability:

  • System.Net.Security.SslStream server-side handshake bounds-checking fixes in TlsFrameHelper close several edge cases that could surface as IOException on malformed ClientHello records.
  • On Linux, certificate-validation failures now surface as standard TLS alerts to the peer, matching Windows behavior. Connecting clients receive an actionable handshake error instead of a connection drop.

Negotiate authentication servers can also validate TLS channel binding tokens on Unix. The managed GSSAPI path passes the channel binding data to gss_accept_sec_context, which rejects a mismatch in the same way as the existing Unix client path. This enables Extended Protection scenarios, such as ASP.NET Core's Negotiate authentication, that bind authentication to the underlying TLS connection.

Experimental caller-driven TLS sessions

System.Net.Security adds a caller-driven, non-blocking TLS state machine for advanced transports where the application controls buffers and I/O scheduling. TlsBufferSession operates over caller-provided spans, while TlsSocketSession works with a SafeSocketHandle.

Operations such as Handshake, Read, Write, Shutdown, and RequestClientCertificate return TlsOperationStatus, which reports whether an operation completed, needs more input, needs a larger destination buffer, or requires certificate validation or a new TlsContext.

HTTP request compression

System.Net.Http adds System.Net.Http.GZipCompressedContent, System.Net.Http.BrotliCompressedContent, and System.Net.Http.ZstandardCompressedContent wrappers for request bodies. These wrappers set the Content-Encoding header and stream compressed content as the request serializes.

Configurable HTTP connection eviction

System.Net.Http.SocketsHttpHandler now provides System.Net.Http.SocketsHttpHandler.ShouldEvictConnection for per-connection eviction decisions. This callback lets you retire connections based on age, endpoint details, and DNS changes instead of fixed lifetime-only policies.

DNS record resolution APIs

System.Net.Dns adds typed record-resolution APIs, including System.Net.Dns.ResolveSrv(System.String), System.Net.Dns.ResolveMx(System.String), System.Net.Dns.ResolveTxt(System.String), System.Net.Dns.ResolveCName(System.String), System.Net.Dns.ResolvePtr(System.String), and System.Net.Dns.ResolveNs(System.String), together with Async variants. The results include records, response code, and negative-cache time-to-live (TTL) metadata through System.Net.DnsResult.

The new System.Net.DnsResolver class provides the same record types through an instance API that accepts a System.Net.DnsResolverOptions object, which lets you supply a custom list of DNS servers instead of using the platform resolver configuration. DnsResolver and the static Dns.Resolve* methods now work on Linux in addition to Windows and macOS.

HTTP/2 automatic downgrade for Windows authentication

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

QUIC stream priority

System.Net.Quic.QuicStream gains a System.Net.Quic.QuicStream.Priority property and a System.Net.Quic.QuicStream.DefaultPriority constant that exposes HTTP/3 stream prioritization (RFC 9218). Priority values range from 0 (highest) to 255 (lowest), with a default of 127:

19

Video MIME type constants

A new System.Net.Mime.MediaTypeNames.Video class provides string constants for common video media types:

  • MediaTypeNames.Video.Mp4 β€” "video/mp4"
  • MediaTypeNames.Video.Mpeg β€” "video/mpeg"
  • MediaTypeNames.Video.Ogg β€” "video/ogg"
  • MediaTypeNames.Video.QuickTime β€” "video/quicktime"
  • MediaTypeNames.Video.WebM β€” "video/webm"

These join the existing MediaTypeNames.Application, MediaTypeNames.Image, MediaTypeNames.Text, and MediaTypeNames.Multipart classes.

HTTP metrics use observable instruments

The high-cardinality HTTP open_connections and active_requests metrics are now observable instruments instead of regular counters. If you use System.Diagnostics.Metrics.MeterListener directly to read these metrics, call System.Diagnostics.Metrics.MeterListener.RecordObservableInstruments to receive measurements from them.

Unsafe API accessibility

.NET 11 removes the [RequiresUnsafe] attribute from a large set of APIs that take pointer parameters. Previously, calling these methods from code that used unsafe blocks still required a project-level <AllowUnsafeBlocks>true</AllowUnsafeBlocks> setting because the attribute enforced that requirement independently. Now, only the standard unsafe block or method modifier is required.

Affected APIs include:

This change reduces friction for advanced scenarios such as interop authoring, memory-mapped files, and low-level buffer manipulation, where unsafe context is already required by the calling code.

See also