20 new APIs in .NET 6

20 new APIs in .NET 6

20 items

Last updated 4/24/2022 8:15 PM
Oleg Kyrylchuk 全球技术精选
11 min read
Category
.NET
Tags
.NET C#

1. DateOnly & TimeOnly

.NET 6 introduces two long-awaited types - DateOnly and TimeOnly, which represent the date and time parts of DateTime respectively.

DateOnly dateOnly = new(2021, 9, 25);
Console.WriteLine(dateOnly);
TimeOnly timeOnly = new(19, 0, 0);
Console.WriteLine(timeOnly);
DateOnly dateOnlyFromDate = DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine(dateOnlyFromDate);
TimeOnly timeOnlyFromDate = TimeOnly.FromDateTime(DateTime.Now);
Console.WriteLine(timeOnlyFromDate);

2. Parallel.ForEachAsync

It allows controlling the degree of parallelism for multiple asynchronous tasks.

var userHandlers = new[]
{
    "users/okyrylchuk",
    "users/jaredpar",
    "users/davidfowl"
};
using HttpClient client = new()
{
    BaseAddress = new Uri("https://api.github.com"),
};
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DotNet", "6"));
ParallelOptions options = new()
{
    MaxDegreeOfParallelism = 3
};
await Parallel.ForEachAsync(userHandlers, options, async (uri, token) =>
{
    var user = await client.GetFromJsonAsync<GitHubUser>(uri, token);
    Console.WriteLine($"Name: {user.Name}\nBio: {user.Bio}\n");
});
public class GitHubUser
{
    public string Name { get; set; }
    public string Bio { get; set; }
}
// Output:
// Name: David Fowler
// Bio: Partner Software Architect at Microsoft on the ASP.NET team, Creator of SignalR
//
// Name: Oleg Kyrylchuk
// Bio: Software developer | Dotnet | C# | Azure
//
// Name: Jared Parsons
// Bio: Developer on the C# compiler

3. ArgumentNullException.ThrowIfNull()

A small improvement for ArgumentNullException. Instead of checking for null in each method before throwing the exception, now you just write one line, similar to response.EnsureSuccessStatusCode();.

ExampleMethod(null);
void ExampleMethod(object param)
{
    ArgumentNullException.ThrowIfNull(param);
    // Do something
}

4. PriorityQueue

A new data structure in .NET 6, PriorityQueue. Each element in the queue has an associated priority that determines the dequeue order. Elements with smaller numbers are dequeued first.

PriorityQueue<string, int> priorityQueue = new();
priorityQueue.Enqueue("Second", 2);
priorityQueue.Enqueue("Fourth", 4);
priorityQueue.Enqueue("Third 1", 3);
priorityQueue.Enqueue("Third 2", 3);
priorityQueue.Enqueue("First", 1);
while (priorityQueue.Count > 0)
{
    string item = priorityQueue.Dequeue();
    Console.WriteLine(item);
}
// Output:
// First
// Second
// Third 2
// Third 1
// Fourth

5. RandomAccess

Provides offset-based APIs for reading and writing files in a thread-safe manner.

using SafeFileHandle handle = File.OpenHandle("file.txt", access: FileAccess.ReadWrite);
// Write to file
byte[] strBytes = Encoding.UTF8.GetBytes("Hello world");
ReadOnlyMemory<byte> buffer1 = new(strBytes);
await RandomAccess.WriteAsync(handle, buffer1, 0);
// Get file length
long length = RandomAccess.GetLength(handle);
// Read from file
Memory<byte> buffer2 = new(new byte[length]);
await RandomAccess.ReadAsync(handle, buffer2, 0);
string content = Encoding.UTF8.GetString(buffer2.ToArray());
Console.WriteLine(content); // Hello world

6. PeriodicTimer

Get to know a fully asynchronous "PeriodicTimer", more suitable for use in async scenarios. It has a method WaitForNextTickAsync.

// One constructor: public PeriodicTimer(TimeSpan period)
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync())
{
    Console.WriteLine(DateTime.UtcNow);
}
// Output:
// 13 - Oct - 21 19:58:05 PM
// 13 - Oct - 21 19:58:06 PM
// 13 - Oct - 21 19:58:07 PM
// 13 - Oct - 21 19:58:08 PM
// 13 - Oct - 21 19:58:09 PM
// 13 - Oct - 21 19:58:10 PM
// 13 - Oct - 21 19:58:11 PM
// 13 - Oct - 21 19:58:12 PM
// ...

7. Metrics API

.NET 6 implements the OpenTelemetry Metrics API specification. It has built-in metrics API, creating the following metrics via the Meter class:

  • Counter
  • Histogram
  • ObservableCounter
  • ObservableGauge

Usage is as follows:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Create Meter
var meter = new Meter("MetricsApp", "v1.0");
// Create counter
Counter<int> counter = meter.CreateCounter<int>("Requests");
app.Use((context, next) =>
{
    // Record the value of measurement
    counter.Add(1);
    return next(context);
});
app.MapGet("/", () => "Hello World");
StartMeterListener();
app.Run();
// Create and start Meter Listener
void StartMeterListener()
{
    var listener = new MeterListener();
    listener.InstrumentPublished = (instrument, meterListener) =>
    {
        if (instrument.Name == "Requests" && instrument.Meter.Name == "MetricsApp")
        {
            // Start listening to a specific measurement recording
            meterListener.EnableMeasurementEvents(instrument, null);
        }
    };
    listener.SetMeasurementEventCallback<int>((instrument, measurement, tags, state) =>
    {
        Console.WriteLine($"Instrument {instrument.Name} has recorded the measurement: {measurement}");
    });
    listener.Start();
}

8. Reflection API to Check Nullability of Elements

It provides nullability information and context from reflection members:

  • ParameterInfo parameters
  • FieldInfo fields
  • PropertyInfo properties
  • EventInfo events
var example = new Example();
var nullabilityInfoContext = new NullabilityInfoContext();
foreach (var propertyInfo in example.GetType().GetProperties())
{
    var nullabilityInfo = nullabilityInfoContext.Create(propertyInfo);
    Console.WriteLine($"{propertyInfo.Name} property is {nullabilityInfo.WriteState}");
}
// Output:
// Name property is Nullable
// Value property is NotNull
class Example
{
    public string? Name { get; set; }
    public string Value { get; set; }
}

9. Reflection API to Check Nullability of Nested Elements

It allows you to get nullability information of nested elements. You can specify that an array property must be non-nullable, but its elements can be nullable, and vice versa.

Type exampleType = typeof(Example);
PropertyInfo notNullableArrayPI = exampleType.GetProperty(nameof(Example.NotNullableArray));
PropertyInfo nullableArrayPI = exampleType.GetProperty(nameof(Example.NullableArray));
NullabilityInfoContext nullabilityInfoContext = new();
NullabilityInfo notNullableArrayNI = nullabilityInfoContext.Create(notNullableArrayPI);
Console.WriteLine(notNullableArrayNI.ReadState);              // NotNull
Console.WriteLine(notNullableArrayNI.ElementType.ReadState);  // Nullable
NullabilityInfo nullableArrayNI = nullabilityInfoContext.Create(nullableArrayPI);
Console.WriteLine(nullableArrayNI.ReadState);                // Nullable
Console.WriteLine(nullableArrayNI.ElementType.ReadState);    // Nullable
class Example
{
    public string?[] NotNullableArray { get; set; }
    public string?[]? NullableArray { get; set; }
}

10. ProcessId & ProcessPath

Get process ID and path directly via Environment.

int processId = Environment.ProcessId
string path = Environment.ProcessPath;
Console.WriteLine(processId);
Console.WriteLine(path);

11. Configuration adds GetRequiredSection()

Similar to DI's GetRequiredService(), it throws an exception if missing.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
MySettings mySettings = new();
// Throws InvalidOperationException if a required section of configuration is missing
app.Configuration.GetRequiredSection("MySettings").Bind(mySettings);
app.Run();
class MySettings
{
    public string? SettingValue { get; set; }
}

12. CSPNG (Cryptographically Secure Pseudo-Random Number Generator)

You can easily generate a random sequence of values from a cryptographically secure pseudo-random number generator (CSPNG).

It is useful in scenarios like:

  • Key generation
  • Random numbers
  • Salts in certain signature schemes
// Fills an array of 300 bytes with a cryptographically strong random sequence of values.
// GetBytes(byte[] data);
// GetBytes(byte[] data, int offset, int count)
// GetBytes(int count)
// GetBytes(Span<byte> data)
byte[] bytes = RandomNumberGenerator.GetBytes(300);

13. Native Memory API

.NET 6 introduces a new API to allocate native memory. NativeMemory has methods to allocate and free memory.

unsafe
{
    byte* buffer = (byte*)NativeMemory.Alloc(100);
    NativeMemory.Free(buffer);
    /* This class contains methods that are mainly used to manage native memory.
    public static class NativeMemory
    {
        public unsafe static void* AlignedAlloc(nuint byteCount, nuint alignment);
        public unsafe static void AlignedFree(void* ptr);
        public unsafe static void* AlignedRealloc(void* ptr, nuint byteCount, nuint alignment);
        public unsafe static void* Alloc(nuint byteCount);
        public unsafe static void* Alloc(nuint elementCount, nuint elementSize);
        public unsafe static void* AllocZeroed(nuint byteCount);
        public unsafe static void* AllocZeroed(nuint elementCount, nuint elementSize);
        public unsafe static void Free(void* ptr);
        public unsafe static void* Realloc(void* ptr, nuint byteCount);
    }*/
}

14. Power of 2

.NET 6 introduces new methods for working with powers of two.

  • 'IsPow2' determines whether the specified value is a power of two.
  • 'RoundUpToPowerOf2' rounds the specified value up to a power of two.
// IsPow2 evaluates whether the specified Int32 value is a power of two.
Console.WriteLine(BitOperations.IsPow2(128));            // True
// RoundUpToPowerOf2 rounds the specified T:System.UInt32 value up to a power of two.
Console.WriteLine(BitOperations.RoundUpToPowerOf2(200)); // 256

15. WaitAsync on Task

You can more easily wait for an asynchronous task to execute. If it times out, a TimeoutException is thrown.

Task operationTask = DoSomethingLongAsync();
await operationTask.WaitAsync(TimeSpan.FromSeconds(5));
async Task DoSomethingLongAsync()
{
    Console.WriteLine("DoSomethingLongAsync started.");
    await Task.Delay(TimeSpan.FromSeconds(10));
    Console.WriteLine("DoSomethingLongAsync ended.");
}
// Output:
// DoSomethingLongAsync started.
// Unhandled exception.System.TimeoutException: The operation has timed out.

16. New Math APIs

New methods:

  • SinCos
  • ReciprocalEstimate
  • ReciprocalSqrtEstimate

New overloads:

  • Min, Max, Abs, Sign, Clamp support nint and nuint
  • DivRem returns a tuple including quotient and remainder.
// New methods SinCos, ReciprocalEstimate and ReciprocalSqrtEstimate
// Simultaneously computes Sin and Cos
(double sin, double cos) = Math.SinCos(1.57);
Console.WriteLine($"Sin = {sin}\nCos = {cos}");
// Computes an approximate of 1 / x
double recEst = Math.ReciprocalEstimate(5);
Console.WriteLine($"Reciprocal estimate = {recEst}");
// Computes an approximate of 1 / Sqrt(x)
double recSqrtEst = Math.ReciprocalSqrtEstimate(5);
Console.WriteLine($"Reciprocal sqrt estimate = {recSqrtEst}");
// New overloads
// Min, Max, Abs, Clamp and Sign supports nint and nuint
(nint a, nint b) = (5, 10);
nint min = Math.Min(a, b);
nint max = Math.Max(a, b);
nint abs = Math.Abs(a);
nint clamp = Math.Clamp(abs, min, max);
nint sign = Math.Sign(a);
Console.WriteLine($"Min = {min}\nMax = {max}\nAbs = {abs}");
Console.WriteLine($"Clamp = {clamp}\nSign = {sign}");
// DivRem variants return a tuple
(int quotient, int remainder) = Math.DivRem(2, 7);
Console.WriteLine($"Quotient = {quotient}\nRemainder = {remainder}");
// Output:
// Sin = 0.9999996829318346
// Cos = 0.0007963267107331026
// Reciprocal estimate = 0.2
// Reciprocal sqrt estimate = 0.4472135954999579
// Min = 5
// Max = 10
// Abs = 5
// Clamp = 5
// Sign = 1
// Quotient = 0
// Remainder = 2

17. CollectionsMarshal.GetValueRefOrNullRef

This is used when iterating over a dictionary or modifying mutable structs. It reduces struct copying and avoids repeated dictionary hash calculations. It's a bit obscure, feel free to check:

Dictionary<int, MyStruct> dictionary = new()
{
    { 1, new MyStruct { Count = 100 } }
};
int key = 1;
ref MyStruct value = ref CollectionsMarshal.GetValueRefOrNullRef(dictionary, key);
// Returns Unsafe.NullRef<TValue>() if it doesn't exist; check using Unsafe.IsNullRef(ref value)
if (!Unsafe.IsNullRef(ref value))
{
    Console.WriteLine(value.Count); // Output: 100
    // Mutate in-place
    value.Count++;
    Console.WriteLine(value.Count); // Output: 101
}
struct MyStruct
{
    public int Count { get; set; }
}

18. ConfigureHostOptions

A new ConfigureHostOptions API on IHostBuilder makes it easier to configure the application.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureHostOptions(o =>
            {
                o.ShutdownTimeout = TimeSpan.FromMinutes(10);
            });
}

19. Async Scope

.NET 6 introduces a new CreateAsyncScope method. When you have services implementing IAsyncDisposable, the existing CreateScope method throws an exception; using CreateAsyncScope solves this perfectly.

await using var provider = new ServiceCollection()
        .AddScoped<Example>()
        .BuildServiceProvider();
await using (var scope = provider.CreateAsyncScope())
{
    var example = scope.ServiceProvider.GetRequiredService<Example>();
}
class Example : IAsyncDisposable
{
    public ValueTask DisposeAsync() => default;
}

20. Simplified Cryptography Classes

  • DecryptCbc
  • DecryptCfb
  • DecryptEcb
  • EncryptCbc
  • EncryptCfb
  • EncryptEcb
static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;
        return aes.DecryptCbc(ciphertext, iv, PaddingMode.PKCS7);
    }
}

End of article...

Author: Oleg Kyrylchuk

Original: https://blog.okyrylchuk.dev/

Keep Exploring

Related Reading

More Articles