.NET 8 Preview 6 Features

Jul 26, 2023 | .NET

Today, we’ve got quite an impressive lineup to talk about – new features and improvements in .NET 8 Preview 6. From major improvements in System.Text.Json to the much-awaited targeting of iOS platforms with NativeAOT, we have a lot to cover.

Between you and me, we programmers have been blessed with a handful of updates that could change the way we build and interact with apps. But, as with all things programming, let’s process these one at a time, shall we?

Yes?

Done

Now let’s start!

System.Text.Json Improvements (or Fixes)

Okay, let’s delve into the nitty-gritty details of the enhancements made to the System.Text.Json source generator. We’re also going to explore how it’s navigating its way to our beloved Native AOT, along with several other amends and upgrades. Excited to see what these modifications bring to our coding table? Of course, you are!

The first thing you’ll notice is the inception of caching support to the incremental generator. This update aims to turbocharge the IDE performance for more substantial projects. Talk about an efficiency upgrade!

They’ve also tweaked the formatting of the source-generated code. This change caters to those pesky indentation issues and minor gripes that have been bugging you. Finally!

Along with that, they’ve introduced new diagnostic warnings – just the thing if you appreciate a bit of a nudge in the right direction.

What? You’re a coder and you’re craving some juicy code examples? Regrettably, Microsoft has kept that thrill for the release. But the anticipation makes it more fun, doesn’t it?

We’re not done yet! They’ve also tackled bugs associated with the infamous accessibility modifier resolution. This upgrade, no doubt, is a massive relief for developers.

Now it’s time to talk about the .JsonStringEnumConverter<TEnum>, an amazing new converter that accompanies the existing JsonStringEnumConverter class. Yes, and just to answer your question, it is indeed supported in Native AOT. Impressed, aren’t you?

If you’re considering targeting Native AOT users, you’ll need to note your enum types in the following format:

// Code snippet to demonstrate the annotation format
[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
public enum MyEnum { Value1, Value2, Value3 }
[JsonSerializable(typeof(MyEnum))]
public partial class MyContext : JsonSerializerContext { }

The provided code snippet depicts usage of the JsonStringEnumConverter. Utilizing this strategy, you can note your enum types, thus aiding in efficient and automatic JSON serialization.

Let’s not forget the brillant JsonConverter.Type property. This cool new bit allows you to figure out the type class of a non-generic JsonConverter instance. Pretty neat, huh?

  // Code snippet showcasing the use of JsonConverter.Type
  Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters)
    => converters.Where(converter => converter.Type != null).ToDictionary(converter => converter.Type!);

With all these enhancements, juggling JSON files in the .NET ecosystem becomes as breezy as a Sunday drive! Are you ready to dive into this feature-rich update? Because I so am!

Stream-based ZipFile CreateFromDirectory and ExtractToDirectory Method Overloads

Hold onto your hats folks, as .NET 8 just made archiving and extracting more efficient with stream-based overloads for the ZipFile.CreateFromDirectory and ZipFile.ExtractToDirectory methods. Seriously, isn’t that nifty?

Let’s dissect this beast, shall we? The CreateFromDirectory method allows you to grab every file from a folder, squish them all into one zip file, and shoot that fun bundle straight into any location you want, and the best part? You do this without needing to save the zipped file onto your hard drive. Magic! We’re talking zero disk footprints and 100% streaming goodness!

And as for the ExtractToDirectory method? Picture this. You have a zip file in a stream, right there, ready to explode its awesome contents onto the filesystem, again without the need for the file to be present on the disk. Diskless extractions for the win!

ZipFile.CreateFromDirectory usages

Here’s an example of how you can use the ZipFile.CreateFromDirectory method. This magic trick squeezes all the files in a directory, zips them and saves them directly into a destination stream. No temporary disk storage required!

// Get a stream to use as a destination
Stream destinationStream = GetStreamFromSomewhere();

// Use the new overload to zip your directory of files and store it in your destination stream
ZipFile.CreateFromDirectory(
    sourceDirectoryName: "/home/username/sourcedirectory/",
    destination: destinationStream,
    compressionLevel: CompressionLevel.Optimal,
    includeBaseDirectory: true,
    entryNameEncoding: Encoding.UTF8);

ZipFile.ExtractToDirectory usage

And now here’s the partner-in-crime, ZipFile.ExtractToDirectory, hard at work! It lets you take a stream with a zipped file and unzip into the filesystem. Pretty cool, huh?

// Get a stream with your source zipped file from somewhere
Stream sourceStream = GetStreamFromSomewhere();

// Here's how you use the method to extract the contents of your zipped stream into a directory in the filesystem
ZipFile.ExtractToDirectory(
    source: sourceStream,
    destinationDirectoryName: "/home/username/destinationdirectory/",
    entryNameEncoding: Encoding.UTF8,
    overwriteFiles: true);

These new overloads for ZipFile.CreateFromDirectory and ZipFile.ExtractToDirectory are powerful tools to optimize resources, especially in environments with disk space constraints. But remember, with great power comes great responsibility.

MetricCollector Metrics API

Do you remember the old InstrumentRecorder? Well, this is his stronger, faster brother. This new class, loaded in the Microsoft.Extensions.Telemetry.Testing pack, is all set to give your testing scenarios a massive boost!

At this point, you’re probably asking “what’s the low-down on this?” Here’s the scoop: MetricCollector doesn’t just record metric measurements anymore. It timestamps them as well.

Now I know all you code-lovers are itching to see MetricCollector at work. So let’s jump into the jungle of code and see this bad boy in action.

MetricCollector usage

// Define the name for your counter
const string CounterName = "MyCounter";

// Set up your time provider
var now = DateTimeOffset.Now;
var timeProvider = new FakeTimeProvider(now);

// Initialise your meter and counter
using var meter = new Meter(Guid.NewGuid().ToString());
var counter = meter.CreateCounter<long>(CounterName);

// Set up your metric collector
using var collector = new MetricCollector<long>(counter, timeProvider);

// Check that nothing has been recorded initially
Assert.Empty(collector.GetMeasurementSnapshot());
Assert.Null(collector.LastMeasurement);

// Add a measurement value to your counter
counter. Add(3);

// Verify that the measurement update was recorded
Assert.Equal(counter, collector.Instrument);
Assert.NotNull(collector.LastMeasurement);
Assert.Single(collector.GetMeasurementSnapshot());
Assert.Same(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.Equal(3, collector.LastMeasurement.Value);
Assert.Empty(collector.LastMeasurement.Tags);
Assert.Equal(now, collector.LastMeasurement.Timestamp);   

So, what’s my verdict? MetricCollector is a hot piece of cake for all your testing needs. Its timestamping feature and the flexibility to use any time provider make it a rockstar in the world of testing metrics. So, this new. NET 8 feature gets a two thumbs-up from me!

Options Validation Source Generator

This feature allows you to validate options right at compile time, reducing the overheads and tracking issues at runtime.

I’ll bet my keyboard’s backspace key, this new addition is here to make error catching faster and brainier than ever. Let’s take a peek into how this modern marvel works!

Understanding Options Validation Usage

Here, we’ve got three models FirstModelNoNamespace, SecondModelNoNamespace and ThirdModelNoNamespace. Each model has properties marked with validation attributes like Required and MinLength.

public class FirstModelNoNamespace
{
    [Required] // Property P1 is required and should have minimum length of 5
    [MinLength(5)]
    public string P1 { get; set; } = string. Empty;

    // P2 and P3 properties are validated using custom validators
    [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidatorNoNamespace))]
    public SecondModelNoNamespace? P2 { get; set; }

    [Microsoft.Extensions.Options.ValidateObjectMembers]
    public ThirdModelNoNamespace? P3 { get; set; }
} 

And the validation logic is implemented in these partial classes FirstValidatorNoNamespace and SecondValidatorNoNamespace.

[OptionsValidator] // Indicates that this class is to be used for options validation
public partial class FirstValidatorNoNamespace : IValidateOptions<FirstModelNoNamespace>
{
}
[OptionsValidator] // Indicates that this class is to be used for options validation
public partial class SecondValidatorNoNamespace : IValidateOptions<SecondModelNoNamespace>
{
}

I love how C# gives us the freedom to keep our code neat and nifty, don’t you?

Now, to inject the validation into the app, all you need to do is add FirstValidatorNoNamespace and SecondValidatorNoNamespace as singleton services in your Startup class, and let the game of checks and balances begin!

var builder = WebApplication.CreateBuilder(args);
// Configuring services
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(builder.Configuration.GetSection(...));

// Adding singleton services for options validation
builder.Services.AddSingleton<IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();

In my opinion, the addition of this feature is a major step up for error management in the .NET eco-system. Now we have an automated system to enjoy error-free code (Well, unless you’re a fan of debugging sessions at 2 AM. No judgments here.)

This makes it easier for us to bear the burden of error management, wouldn’t you agree? Plus, it provides awesome benefits like saving CPU cycles and impromptu headaches.

LoggerMessageAttribute’s Extended Constructor Overloads

Now, as you may know, the existing constructor of a LoggerMessageAttribute requires specifying the EventId, LogLevel, and message parameters. But let’s be real, not always we need to define all of them, right? And this is where these newly introduced overloads come into play, bringing a new level of ease and versatility to the table. No more need for additional parameters if they are not necessary!

For instance, check out this sleek code showing the new available constructors. Aren’t they a exciting sight?

    // Specifying only the LogLevel and message
    public LoggerMessageAttribute(LogLevel level, string message);

    // Specifying only the LogLevel
    public LoggerMessageAttribute(LogLevel level);

    // Specifying only the message
    public LoggerMessageAttribute(string message);

And here’s a bit more enticing bit. Take a look at this user-pleasing invocation of LoggerMessageAttribute in a Logger method. Now raise your hands if the thought of not having to specify EventId each time makes you as giddy as it makes me?

    //LogLevel and Message parameters are declared
    [LoggerMessage(Level = LogLevel.Warning, Message = "{p1} should be valid")]
    public partial void LogWarning(string p1);

Before you ask, let me assure you! Microsoft has mentioned that for those constructors where EventId is not required, the system will auto-magically take care of it and generate it for us in future previews; giving us yet another reason to celebrate this new feature.

Did anybody ask for easier logging in .NET? Well, your wishes were heard loud and clear! So, what’s your take on this feature? Are you as excited as I am to reduce unnecessary code and log like a breeze?

Source Generated COM Interop

We’ve got an “improved” source generator in our hands that plays well with COM interfaces. This is all thanks to the new-gen interop support, initialized with LibraryImportAttribute, springing off this update. You’ll see this new System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute doing the magic of marking an interface as a COM one for the source generator. Want to know something even more exciting? This source generator will scribble down code that enables C# code callbacks to unmanaged code and vice versa!

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

[GeneratedComInterface] // We've marked the interface with the given attribute, which indicates that this is a COM interfate.
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
interface IComInterface
{
    void DoWork();
}

internal class MyNativeLib
{
    [LibraryImport(nameof(MyNativeLib))]
    public static partial void GetComInterface(out IComInterface comInterface); // The source generator part to enable the interoperability
}

Alright, you might say, “Where’s the catch?”

Well, here it is.

The generator supports yet another new attribute, the System.Runtime.InteropServices.Marshalling.GeneratedComClassAttribute. This cool attribute lets you pass on types that implement interfaces with System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute-attributed interfaces to unmanaged code.

MyNativeLib.GetComInterface(out IComInterface comInterface); // Getting the COM interface
comInterface.RegisterCallbacks(new MyCallbacks()); // Registering the callbacks, which is exposed to unmanaged code
comInterface.DoWork(); 

//... More code ...

[GeneratedComClass]
internal class MyCallbacks : ICallbacks // COM class attribute being used for the class implementing the interface
{
    public void Callback() // A callback implementation
    {
        Console.WriteLine("Callback called");
    }
}

Now, interfaces with GeneratedComInterfaceAttribute can also interact with LibraryImportAttribute. They both support each other’s types, which is a big win.

There’s a bit of a caveat, though. There are few support limitations in the COM source generator. Interface types such as IDispatch and IInspectable, apartment affinity, COM properties, COM events, and using the new keyword to activate a COM CoClass are not supported. Don’t worry, they’ve already got improvements in the pipeline for future .NET releases.

SHA-3 Support

What’s new in .NET 8 Preview 6? We’ve got SHA-3 hashing primitives support added to our roster! Allow me to simplify: Think of it as a shiny new tool in your crypto toolkit. However, keep in mind that these SHA-3 hashing mechanisms are specifically accessible on systems running OpenSSL 1.1.1+ or Windows 11 builds numbered 25324 and above.

Why Does It Matter?

Well, in today’s world where data protection is worth its weight in gold, an extra layer of a robust cryptographic hash like SHA-3 is nothing short of a gift from the heavens. This chest of cryptographic treasure includes variants such as SHA3_256, SHA3_384, and SHA3_512 for carrying out hashing; it’s even equipped with HMAC versions and is particularly tailored for RSA OAEP encryption. Quite the security asset, don’t you think?

Let’s walk through some code snippets to see it in action.

// Hashing example
// Check if SHA-3-256 is supported on the current platform.
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash); // Hashing made easier.
}
else
{
    // Determine what to do if SHA-3 is not supported.
    // Backup plan?
}

// Signing Example
// Check if SHA-3-256 is supported on the current platform.
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCuves.nistP256); // Creating an elliptic curve.
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256); // Signing data with SHA-3-256.
}
else
{
    // Determine what to do if SHA-3 is not supported.
    // Maybe an alternative hash algorithm?
}

Easy, right? Just check if SHA-3-256 (or any SHA-3 variation) is supported and… voila!

SHAKE (Don’t confuse it with a milkshake)

Oh, it gets better! SHA-3 boasts two flexible output functions, aptly named SHAKE128 and SHAKE256. These aren’t your typical functions, they can generate output of any length. Hence, the term “extendable”. Cool, isn’t it?

if (Shake128.IsSupported)
{
    using Shake128 shake = new Shake128(); // Create a new instance.
    shake.AppendData("Hello .NET!"u8); // Data to shake.
    byte[] digest = shake.GetHashAndReset(outputLength: 32); // Shake it up!

    // You can also use it like this:
    digest = Shake128.HashData("Hello .NET!"u8, outputLength: 32);
}
else
{
    // Determine what to do if SHAKE is not supported.
    // Time to shake up the plan. 
}

Now, I must add a little caveat here: SHA-3 support is primarily aimed at supporting cryptographic primitives. So don’t go all giddy, expecting it to fully support higher-level constructions and protocols. At least not yet.

More context about SHA-3

Ever heard of SHA-3? It’s a neat standard established by the tech bods at NIST (under the official-sounding FIPS 202). But here’s the cool part: It’s an alternative to SHA-2, not a replacement. So the ball’s really in your court: Whether and when you want to use SHA-3 is totally your call or that of your team or business!

But – and here’s the big question: Why would you resist stepping into a better hashing future when it’s literally at your fingertips? SHA-3, my friends, is an excellent addition to our developer toolkit, bringing with it fresh possibilities for securing your applications. If you ask me, I’m definitely going to adopt it.

Support for Targeting iOS Platforms with NativeAOT

Well, here’s a big one! Microsoft has now rolled out support for targeting iOS platforms with NativeAOT. Now, you can create and operate .NET iOS and .NET MAUI apps using NativeAOT. This applies to iOS platforms like iossimulator, maccatalyst, tvos, and tvossimulator. It’s like building and running your apps on all of these Apple devices. Amazing, right?

Now, before you get too excited, let’s dig a bit deeper and understand this feature better.

Working Principle

Though this feature is available, it’s still an opt-in feature, which means it’s not your default runtime for app development and deployment. That crown still goes to Mono. However, It does paint an interesting picture for future developments in terms of performance and size reductions on these platforms.

Current Status and Findings

Of course, in this case Microsoft has been doing a lot of tests and benchmarks. The results that Microsoft has shared this time are these:

  • Test on a .NET iOS app (created using dotnet new ios):
image 34

The .NET iOS app shows incredible improvements with NativeAOT, exhibiting a near 40% reduction in size on disk and .ipa size. It’s like your app just went on a successful diet!

  • Test on a .NET MAUI iOS app (created using dotnet new maui):
image 35

Unlike its .NET iOS counterpart, the .NET MAUI app has some flexing to do before it gets lean. The size on disk and .ipa size have unfortunately increased with NativeAOT. But hey, no cause for alarm, the guys at Microsoft are on it!

More context

We need to bear in mind that this is still a work-in-progress and even though initial testing seems promising, we cannot take these numbers as conclusive results. The real-world application and testing of this feature will reveal more in-depth details.

Nevertheless, I’m personally excited about this update. If Microsoft pulls it off, it will bring a major shift in terms of efficiency and performance in targeting the iOS platform with .NET. For now, I advise you to play around with it, explore, and see what you think.

Conclusion

I hope you found it as interesting navigating through it as I did explaining it. From smaller but game-changing features like Stream-based ZipFile method overloads, MetricCollector Metrics API, to larger impact ones such as the support for targeting iOS platforms with NativeAOT, each feature has its special place in making .NET better. Some features need a bit more polishing, like the Options Validation Source Generator, but hey, Rome wasn’t built in a day, was it?

You May Also Like

Sign up For Our Newsletter

Weekly .NET Capsules: Short reads for busy devs.

  • NLatest .NET tips and tricks
  • NQuick 5-minute reads
  • NPractical code snippets
.