7 New Features (or bug fixesđ) You Might Have Missed in .NET 8
ByteHide
Posted on July 26, 2023
TLDR's TLDR: Fixes, fixes, fixesâŠ, SHA-3 Support, Support for targeting iOS platforms with NativeAOT and more!
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.
Here's the Microsoft code example.
// 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?
Here's the Microsoft code example.
// 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!
Here's the Microsoft code example.
// 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?
Here's the Microsoft code example.
// 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
Here's the Microsoft code example.
// 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
.
Here's the Microsoft code example.
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
.
Here's the Microsoft code example.
[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!
Here's the Microsoft code example.
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?
Here's the Microsoft code example.
// 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!
Here's the Microsoft code example.
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.
Here's the Microsoft code example.
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.
Here's the Microsoft code example.
// 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?
Here's the Microsoft code example.
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
):
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
):
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?
Posted on July 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.