August 23rd, 2021

New .NET 6 APIs driven by the developer community

David Fowler
Distinguished Engineer

.NET 6 is on the way, and I wanted to share some of my favorite new APIs in .NET and ASP.NET Core that you are going to love. Why are you going to love them? Well because they were directly driven by our fantastic .NET developer community! Let’s get started!

Reading & Writing Files

In .NET 6, there’s a new low-level API to enable reading/writing of files without using a FileStream. It also supports scatter/gather IO (multiple buffers) and overlapping reads and writes at a given file offset.

using Microsoft.Win32.SafeHandles;
using SafeFileHandle handle = File.OpenHandle("ConsoleApp128.exe");
long length = RandomAccess.GetLength(handle);

Console.WriteLine(length);

Process Path & Id

There are a couple of new ways to access a process path and process id without allocating a new process object:

int pid = Environment.ProcessId;
string path = Environment.ProcessPath;

Console.WriteLine(pid);
Console.WriteLine(path);

CSPNG (Cryptographically Secure Pseudorandom Number Generator)

Generating random numbers from a CSPNG (Cryptographically Secure Pseudorandom Number Generator) is a easier than ever:

// Give me 200 random bytes
byte[] bytes = RandomNumberGenerator.GetBytes(200);

Parallel.ForEachAsync

We finally added Parallel.ForEachAsync, a way to schedule asynchronous work that allows you to control the degree of parallelism:

var urlsToDownload = new [] 
{
    "https://dotnet.microsoft.com",
    "https://www.microsoft.com",
    "https://twitter.com/davidfowl"
};

var client = new HttpClient();

await Parallel.ForEachAsync(urlsToDownload, async (url, token) =>
{
    var targetPath = Path.Combine(Path.GetTempPath(), "http_cache", url);

    HttpResponseMessage response = await client.GetAsync(url);

    if (response.IsSuccessStatusCode)
    {
        using FileStream target = File.OpenWrite(targetPath);

        await response.Content.CopyToAsync(target);
    }
});

Configuration Helpers

We added a helper to make it easier to throw if a required section of configuration is missing:

var configuration = new ConfigurationManager();
var options = new MyOptions();

// This will throw if the section isn't configured
configuration.GetRequiredSection("MyOptions").Bind(options);

class MyOptions
{
    public string? SettingValue { get; set;}
}

LINQ

There’s a ton of new LINQ methods as well. It got lots of love in this release. Here’s a new helper to chunk any IEnumerable into batches:

int chunkNumber = 1;
foreach (int[] chunk in Enumerable.Range(0, 9).Chunk(3))
{
    Console.WriteLine($"Chunk {chunkNumber++}");
    foreach (var item in chunk)
    {
        Console.WriteLine(item);
    }
}

Even More LINQ!

More LINQ! There are now MaxBy and MinBy methods:

var people = GetPeople();

var oldest = people.MaxBy(p => p.Age);
var youngest = people.MinBy(p => p.Age);

Console.WriteLine($"The oldest person is {oldest.Age}");
Console.WriteLine($"The youngest person is {youngest.Age}");

public record Person(string Name, int Age);

Power of 2

Don’t keep bit math in your head? Me neither. Here are some new helpers for working with powers of 2:

using System.Numerics;

uint bufferSize = 235;
if (!BitOperations.IsPow2(bufferSize))
{
    bufferSize = BitOperations.RoundUpToPowerOf2(bufferSize);
}

Console.WriteLine(bufferSize);

WaitAsync Improvements

There’s now a much easier (and properly implemented) way to wait for task to complete asynchronously. The following code will yield the await if it hasn’t completed in 10 seconds. The operation might still be running! This is for un-cancellable operations!

Task operationTask = SomeLongRunningOperationAsync();

await operationTask.WaitAsync(TimeSpan.FromSeconds(10));

ThrowIfNull

No more having to check for null in every method before you throw an exception. It is now just one line of code.

void DoSomethingUseful(object obj)
{
    ArgumentNullException.ThrowIfNull(obj);
}

Working with NativeMemory

If you want to use C APIs to allocate memory because you’re a l33t hacker or need to allocate native memory, then look no further. Don’t forget to free!

using System.Runtime.InteropServices;

unsafe
{
    byte* buffer = (byte*)NativeMemory.Alloc(100);

    NativeMemory.Free(buffer);
}

Posix Signal Handling

Native support for Posix signal handling is here and we also emulate a couple of signals on Windows.

using System.Runtime.InteropServices;

var tcs = new TaskCompletionSource();

PosixSignalRegistration.Create(PosixSignal.SIGTERM, context =>
{
    Console.WriteLine($"{context.Signal} fired");
    tcs.TrySetResult();
});

await tcs.Task;

New Metrics API

We added an entirely new metrics API based on @opentelemetry in .NET 6. It supports dimensions, is super efficient and will have exporters for popular metric sinks.

using System.Diagnostics.Metrics;

// This is how you produce metrics

var meter = new Meter("Microsoft.AspNetCore", "v1.0");
Counter<int> counter = meter.CreateCounter<int>("Requests");

var app = WebApplication.Create(args);

app.Use((context, next) =>
{
    counter.Add(1, KeyValuePair.Create<string, object?>("path", context.Request.Path.ToString()));
    return next(context);
});

app.MapGet("/", () => "Hello World");

You can even listen in and meter:

var listener = new MeterListener();
listener.InstrumentPublished = (instrument, meterListener) =>
{
    if(instrument.Name == "Requests" && instrument.Meter.Name == "Microsoft.AspNetCore")
    {
        meterListener.EnableMeasurementEvents(instrument, null);
    }
};

listener.SetMeasurementEventCallback<int>((instrument, measurement, tags, state) =>
{
    Console.WriteLine($"Instrument: {instrument.Name} has recorded the measurement: {measurement}");
});

listener.Start();

Modern Timer API

Last but not least, a modern timer API (I think this is the 5th timer API in .NET now). It’s fully async and isn’t plagued by the types of gotchas the other timers are plagued with like object lifetime issues, no asynchronous callbacks etc.

var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));

while (await timer.WaitForNextTickAsync())
{
    Console.WriteLine(DateTime.UtcNow);
}

Summary

This is just a sampling of the new APIs coming in .NET 6. For more information look at the .NET 6 release notes API diffs. Also, Stephen just wrote a spectacular blog on performance improvements in .NET6 so be sure to give that a read. Finally, don’t forget to download the .NET 6 Preview and try out the new APIs today.

Author

David Fowler
Distinguished Engineer

David Fowler is a Partner Software Architect on the .NET Team. He works on ASP.NET Core and is the creator of SignalR. He's also the architect for the Azure SignalR Service.

59 comments

Discussion is closed. Login to edit/delete existing comments.

  • Jon Miller

    Glad there’s the new Chunk() method. I’ve been wanting that one for awhile and had to write my own which wasn’t very efficient.

  • Jon Miller

    Regarding Process Path and Id. When are you going to add a way to access the process command-line arguments?

  • Andriy Savin

    Hi, in the example with the timer, what would be the difference compared to using Task.Delay in that loop?

    • David FowlerMicrosoft employee Author

      It behaves a little differently
      – If your code runs longer than the period, then WaitForNextTickAsync will complete immediately
      – It’s more efficient as it’s single reusable timer and awaitable allocation vs a timer and task per call.

      • Andriy Savin

        Thanks!

  • Ankit Vijay

    ArgumentNullException.ThrowIfNull(obj) is great, but getting ArgumentException.ThrowIfNullOrEmpty(stringObj) and ArgumentException.ThrowIfNullOrWhiteSpace(stringObj) would be an icing on the cake.

    • Andriy Savin

      I agree with you, but if you add parameter name to each of these, these checks will be too long to read. Even ArgumentNullException.ThrowIfNull is not good in this sense. I’d prefer to see something like EF team uses in their code: Check.NotNull or Check.NotEmpty.

  • Reelix · Edited

    If you want developer driven upgrades, how about addressing the following .NET limitations :)

    1.) The native Zip class (System.IO.Compression.ZipArchive) does not support decompressing password-protected zip files - A third-party library is required.
    2.) The native FTP class (System.Net.FtpWebRequest) does not support listing file names/directories starting with a . on an FTP Server - A third-party library is required.
    3.) The native Active Directory / LDAP class (System.DirectoryServices.*) does not support connecting to a secured Windows...

    Read more
    • Tom Bruyneel

      You can always suggest them on github and gain traction for them to get them implemented 🙂

  • Alejandro Sosa · Edited

    Hi David, As an old Visual FoxPro developer attempting a full dunk into .NET by converting, not migrating, a VFP application to .NET, I welcome the .NET 6 goal of more simplification and less ceremony. Keep it up. Anything that reduces the learning curve, like global using, is appreciated.

  • Richard Deeming

    I think some of the XML doc comments could do with a proof-reader! 🙂

    For example: Chunk

    An IEnumerable{T} whose elements to chunk.
    • silkfire

      Looks like perfectly good English to me.

  • Peter Wong

    Is ThrowIfNull any different from the C# 9 simplified parameter null validation?

    void DoSomethingUseful(object obj!)
    {
        ...
    }
    • Huo Yaoyuan

      The language feature has been delayed again and won’t ship with C# 10.

      Once implemented, the language feature will likely generate calls to ThrowIfNull. Calling it explicitly will give you more control then (For example when null is acceptable in some states).

  • Rand Random · Edited

    I think this is the 5th timer API in .NET now

    Had the same thought, when I read the title but still, why didn't you mark the "old" timers obsolete?

    May I ask why you phrased it as "We added..", IMHO "We" seems to imply that Microsoft Team did it, which seems condraditing the first paragrah that wanted to highlight the community driven APIs.

    Read more
    • David FowlerMicrosoft employee Author

      The greater “we”. “We the .NET people”. On a more serious note, APIs that are suggested by the community aren’t always implemented by the community.

  • Vincent Thorn

    In the section ‘Reading & Writing Files’ I totally miss what’s helpful in this mess. Where is “reading & writing”?? How it helps me above existing “File.WriteAllBytes”?
    Random access – it was existing before, what new functions are introduced and how they improve old code? Any sample?

    • Dan MoseleyMicrosoft employee

      See the original proposal here

      In short, it enables scenarios that need the highest performance, lowest allocation thread safe file access. For most people, the existing API remain good choices.