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
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:
- System.Diagnostics.Process.Run and System.Diagnostics.Process.RunAsync β launch a process and return an exit-status result.
- System.Diagnostics.Process.RunAndCaptureText and System.Diagnostics.Process.RunAndCaptureTextAsync β launch and capture stdout/stderr together with exit code.
- System.Diagnostics.Process.ReadAllText(System.Nullable{System.TimeSpan}), System.Diagnostics.Process.ReadAllBytes(System.Nullable{System.TimeSpan}), and their async variants β read a child process's standard output in a single call.
- System.Diagnostics.Process.ReadAllLines(System.Nullable{System.TimeSpan}) β synchronously reads all lines of output from a child process, returning a sequence of System.Diagnostics.ProcessOutputLine values that distinguish stdout from stderr.
- System.Diagnostics.Process.ReadAllLinesAsync(System.Threading.CancellationToken) β returns a stream of System.Diagnostics.ProcessOutputLine values that distinguish stdout from stderr without string parsing.
Fire-and-forget launches
- System.Diagnostics.Process.StartAndForget β starts a child process when you don't intend to wait for it; the runtime detaches the handle automatically.
- System.Diagnostics.ProcessStartInfo.StartDetached β detaches from the parent's session/console so the child can outlive a terminal exit.
- System.Diagnostics.ProcessStartInfo.KillOnParentExit (Windows only) β the child is terminated when the parent process exits.
SafeProcessHandle lifecycle methods
Microsoft.Win32.SafeHandles.SafeProcessHandle gains lifecycle methods for advanced scenarios:
- Microsoft.Win32.SafeHandles.SafeProcessHandle.Start(System.Diagnostics.ProcessStartInfo) and a new Microsoft.Win32.SafeHandles.SafeProcessHandle.ProcessId property β launch and identify processes without going through System.Diagnostics.Process itself.
- Microsoft.Win32.SafeHandles.SafeProcessHandle.Kill and Microsoft.Win32.SafeHandles.SafeProcessHandle.Signal(System.Runtime.InteropServices.PosixSignal) β terminate or signal a process by handle.
- Microsoft.Win32.SafeHandles.SafeProcessHandle.WaitForExit and Microsoft.Win32.SafeHandles.SafeProcessHandle.WaitForExitAsync(System.Threading.CancellationToken) β wait for a process to exit by handle.
Tighter handle control
- System.Diagnostics.ProcessStartInfo.InheritedHandles β specify exactly which OS handles a child process inherits, instead of using the all-or-nothing
UseShellExecute = falsedefault. - System.Diagnostics.ProcessStartInfo.StandardInputHandle, System.Diagnostics.ProcessStartInfo.StandardOutputHandle, and System.Diagnostics.ProcessStartInfo.StandardErrorHandle β supply already-open Microsoft.Win32.SafeHandles.SafeFileHandle values for redirection without the framework opening new ones.
Suspended starts and process lookup
System.Diagnostics.Process adds finer control over starting and finding processes:
- System.Diagnostics.ProcessStartInfo.StartSuspended starts a process in a suspended state on Windows and macOS. Pair it with Microsoft.Win32.SafeHandles.SafeProcessHandle.Resume to let it run once you've finished any setup, such as attaching a debugger or configuring job objects.
- System.Diagnostics.Process.TryGetProcessById(System.Int32,System.Diagnostics.Process@) returns
falseinstead of throwing when no process with the given ID exists. - Microsoft.Win32.SafeHandles.SafeProcessHandle.Open(System.Int32) and Microsoft.Win32.SafeHandles.SafeProcessHandle.TryOpen(System.Int32,Microsoft.Win32.SafeHandles.SafeProcessHandle@) open a handle to an existing process by ID.
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:
- System.Diagnostics.Process.Signal(System.Runtime.InteropServices.PosixSignal) sends a POSIX signal to the process.
- System.Diagnostics.Process.WaitForExitStatus, System.Diagnostics.Process.TryWaitForExitStatus(System.TimeSpan,System.Diagnostics.ProcessExitStatus@), and System.Diagnostics.Process.WaitForExitStatusAsync(System.Threading.CancellationToken) return a System.Diagnostics.ProcessExitStatus, which distinguishes a normal exit from a signal-based termination.
sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csProcessSignal
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:
FORCE_COLOR=1 dotnet run | tee build.logIn-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:
ReadOnlyMemoryStreamexposes aReadOnlyMemory<byte>as a read-only stream.WritableMemoryStreamexposes a writableMemory<byte>as a fixed-size stream.ReadOnlySequenceStreamexposes aReadOnlySequence<byte>(for example, buffers fromSystem.IO.Pipelines) as a stream without flattening it.StringStreamreads astringorReadOnlyMemory<char>as a stream using a specified encoding.
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
- Base64 encoding improvements
- UTF validation and invalid-subsequence search
- System.Text.Json improvements
- Regular expression improvements
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:
Contains- Check if a string contains a specific rune: System.String.Contains(System.Text.Rune) and System.String.Contains(System.Text.Rune,System.StringComparison).StartsWithandEndsWith- Check if a string starts or ends with a specific rune: System.String.StartsWith(System.Text.Rune), System.String.StartsWith(System.Text.Rune,System.StringComparison), System.String.EndsWith(System.Text.Rune), and System.String.EndsWith(System.Text.Rune,System.StringComparison).IndexOfandLastIndexOf- Find the position of a rune in a string: System.String.IndexOf(System.Text.Rune), System.String.IndexOf(System.Text.Rune,System.StringComparison), System.String.LastIndexOf(System.Text.Rune), and System.String.LastIndexOf(System.Text.Rune,System.StringComparison).Replace- Replace occurrences of one rune with another: System.String.Replace(System.Text.Rune,System.Text.Rune).Split- Split a string using a rune as the separator: System.String.Split(System.Text.Rune,System.StringSplitOptions) and System.String.Split(System.Text.Rune,System.Int32,System.StringSplitOptions).Trim,TrimStart, andTrimEnd- Trim runes from strings: System.String.Trim(System.Text.Rune), System.String.TrimStart(System.Text.Rune), and System.String.TrimEnd(System.Text.Rune).
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
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:
- Encoding to chars: System.Buffers.Text.Base64.EncodeToChars and System.Buffers.Text.Base64.EncodeToString
- Encoding to UTF-8: System.Buffers.Text.Base64.EncodeToUtf8
- Decoding from chars: System.Buffers.Text.Base64.DecodeFromChars
- Decoding from UTF-8: System.Buffers.Text.Base64.DecodeFromUtf8
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).
UTF validation and invalid-subsequence search
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
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
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:
- System.Text.Json.JsonNamingPolicy.PascalCase: A new built-in naming policy that converts property names to PascalCase. It joins the existing camelCase, snake_case, and kebab-case policies.
- Per-member naming policy: The new System.Text.Json.Serialization.JsonNamingPolicyAttribute attribute lets you override the naming policy on individual properties or fields, giving you fine-grained control without a custom converter.
- Type-level ignore conditions: Applying System.Text.Json.Serialization.JsonIgnoreAttribute at the class or struct level sets the default ignore behavior for all members, so you no longer need to repeat the attribute on every nullable property.
sample Β· csharpdocs/core/whats-new/dotnet-11/snippets/csharp/Libraries.csJsonNamingIgnore
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: