Skip to main content

Newsletter Archive

Browse through our collection of past newsletters. Each edition is packed with C# and .NET insights.

Page 1 of 19 (55 editions)

October 4, 2026

You need to run git status from a .NET tool and capture the result.

That sounds like five lines of code. The old Process API could turn it into redirection settings, output events, error events, task completion sources, and a small prayer that both streams finish before the process exits.

.NET 11 adds one-shot process helpers for the common case.

This tip uses .NET 11 Preview 7. Preview APIs can change before the final release.

Try It

Install the .NET 11 Preview 7 SDK, then create a console app:

Terminal window
dotnet new console -n ProcessCaptureDemo -f net11.0
cd ProcessCaptureDemo

Replace Program.cs with this:

using System.Diagnostics;
ProcessTextOutput result = await Process.RunAndCaptureTextAsync(
"git",
["status", "--short"]);
Console.WriteLine($"Exit code: {result.ExitStatus.ExitCode}");
if (!string.IsNullOrWhiteSpace(result.StandardOutput))
{
Console.WriteLine("Standard output:");
Console.WriteLine(result.StandardOutput);
}
if (!string.IsNullOrWhiteSpace(result.StandardError))
{
Console.WriteLine("Standard error:");
Console.WriteLine(result.StandardError);
}

Run it from any Git repository:

Terminal window
dotnet run

You get standard output, standard error, and the exit code in one result. No event handlers required.

Stream Output as It Arrives

When you need live progress instead of one final string, .NET 11 also adds ReadAllLinesAsync:

using var process = Process.Start(new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "--info",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
})!;
await foreach (ProcessOutputLine line in
process.ReadAllLinesAsync(CancellationToken.None))
{
var label = line.StandardError ? "ERR" : "OUT";
Console.WriteLine($"[{label}] {line.Content}");
}

The output type tells you which stream produced each line, so you don’t have to merge two event pipelines yourself.

When Not to Use It

RunAndCaptureTextAsync holds the captured text in memory. That is perfect for a command such as git status. It is not the right choice for a process that can print gigabytes or run forever. Stream those lines or redirect them to a file instead.

Key Takeaway

.NET 11 gives Process a much friendlier path for running commands and collecting results. Use the one-shot helper for bounded output. Use the async line stream when you need progress as it happens.

Read the full tip + 5 curated links

Don't miss the next tip 💧

Get a .NET tip and curated links delivered to your inbox every week.

September 27, 2026

You have a large folder to archive inside a web request or desktop app. The ZIP APIs do the job, but the synchronous call keeps a thread occupied while the disk does all the real work.

The usual workaround was to wrap it in Task.Run. That moves the blocked thread. It doesn’t make the file work asynchronous.

.NET 10 adds real async APIs for creating, opening, updating, and extracting ZIP archives.

Try It

Create a .NET 10 console app:

Terminal window
dotnet new console -n AsyncZipDemo -f net10.0
cd AsyncZipDemo

Replace Program.cs with this:

using System.IO.Compression;
var workspace = Path.Combine(Path.GetTempPath(), "async-zip-demo");
var source = Path.Combine(workspace, "source");
var extracted = Path.Combine(workspace, "extracted");
var archive = Path.Combine(workspace, "backup.zip");
if (Directory.Exists(workspace))
{
Directory.Delete(workspace, recursive: true);
}
Directory.CreateDirectory(source);
await File.WriteAllTextAsync(
Path.Combine(source, "hello.txt"),
"Hello from .NET 10!");
await File.WriteAllTextAsync(
Path.Combine(source, "settings.json"),
"""{"theme":"dark","fontSize":18}""");
await ZipFile.CreateFromDirectoryAsync(
source,
archive,
CompressionLevel.Optimal,
includeBaseDirectory: false,
cancellationToken: CancellationToken.None);
await ZipFile.ExtractToDirectoryAsync(
archive,
extracted,
overwriteFiles: true,
cancellationToken: CancellationToken.None);
foreach (var file in Directory.EnumerateFiles(extracted))
{
Console.WriteLine($"{Path.GetFileName(file)}: {await File.ReadAllTextAsync(file)}");
}

Run it:

Terminal window
dotnet run

More Than Two Helper Methods

.NET 10 also adds async APIs lower in the stack:

await using var file = File.OpenRead("backup.zip");
using var archive = await ZipArchive.CreateAsync(
file,
ZipArchiveMode.Read,
leaveOpen: false,
entryNameEncoding: null,
cancellationToken: cancellationToken);
foreach (var entry in archive.Entries)
{
await using var entryStream = await entry.OpenAsync(cancellationToken);
// Read the entry without blocking a thread on file I/O.
}

That matters when you are working with uploaded streams, cloud storage, or archives that never touch a local filename.

When Not to Use It

Async doesn’t make compression itself free. The CPU work still has to happen. The win is that file and stream waits no longer pin a thread.

For a tiny ZIP created once in a console tool, the synchronous API is perfectly fine. Reach for async when the archive is large, the storage is slow, cancellation matters, or blocking a request thread would hurt.

Key Takeaway

.NET 10 gives ZIP files honest async support. You can create, extract, open, and update archives without hiding synchronous I/O inside Task.Run.

Read the full tip + 6 curated links

Don't miss the next tip 💧

Get a .NET tip and curated links delivered to your inbox every week.

September 20, 2026

JSON from the outside world can get weird.

A payload can repeat the same property twice, slip in fields you never expected, or leave out a constructor value your code needs. The default System.Text.Json settings try to be friendly about most of that.

Sometimes friendly is the last thing you want.

.NET 10 adds JsonSerializerOptions.Strict, a ready-made set of options for treating your JSON contract like an actual contract.

Try It

Create a .NET 10 console app:

Terminal window
dotnet new console -n StrictJsonDemo -f net10.0
cd StrictJsonDemo

Replace Program.cs with this:

using System.Text.Json;
const string validJson =
"""{"Id":42,"Product":"Mechanical Keyboard"}""";
const string duplicateProperty =
"""{"Id":42,"Product":"Keyboard","Product":"Mystery Box"}""";
const string unknownProperty =
"""{"Id":42,"Product":"Keyboard","Coupon":"FREE-STUFF"}""";
Read("Valid", validJson);
Read("Duplicate property", duplicateProperty);
Read("Unknown property", unknownProperty);
static void Read(string label, string json)
{
try
{
var order = JsonSerializer.Deserialize<Order>(
json,
JsonSerializerOptions.Strict);
Console.WriteLine($"{label}: accepted {order}");
}
catch (JsonException ex)
{
Console.WriteLine($"{label}: rejected");
Console.WriteLine($" {ex.Message}");
}
}
public sealed record Order(int Id, string Product);

Run it:

Terminal window
dotnet run

The valid payload works. The duplicate and unknown properties throw JsonException.

What Strict Mode Turns On

The preset combines several settings:

var options = JsonSerializerOptions.Strict;
Console.WriteLine(options.AllowDuplicateProperties); // False
Console.WriteLine(options.UnmappedMemberHandling); // Disallow
Console.WriteLine(options.RespectNullableAnnotations); // True
Console.WriteLine(options.RespectRequiredConstructorParameters); // True
Console.WriteLine(options.PropertyNameCaseInsensitive); // False

You can also clone the preset when you need to adjust another setting:

var options = new JsonSerializerOptions(JsonSerializerOptions.Strict)
{
WriteIndented = true
};

When Not to Use It

Strict mode is a great fit when you own both sides of an API or you need tight validation at a boundary.

It can be painful when you consume a third-party API that adds fields without warning. Rejecting every unknown property means a harmless upstream addition can break your integration. In that situation, choose the individual strict settings that protect you without making the contract brittle.

Key Takeaway

JsonSerializerOptions.Strict gives you a safer JSON baseline with one preset. It catches duplicate properties, unexpected fields, missing required constructor values, and nullability problems before questionable data wanders deeper into your app.

Read the full tip + 5 curated links

Don't miss the next tip 💧

Get a .NET tip and curated links delivered to your inbox every week.