Skip to main content

Microsoft does not stop! For months now we have seen updates and news from the .NET development team and they have just released the third preview of .NET 7. This final version is planned for release in November this year, but until then let’s talk about the new features and performance improvements it will bring!

.NET 7 Release Date

If you are wondering when .NET 7 will be officially released, according to Microsoft .NET 7 will be officially released in November 2022 and will replace the current version of .NET 6.

Currently .NET 7 has several previews and can already be tested and according to Microsoft .NET 7 has been tested with the latest Visual Studio preview.

.NET Conf 2022

In the .NET Conf 2022, Microsoft presented a variety of exciting new features and capabilities for .NET 7. At the event Microsoft showcased new tools and features such as Visual Studio Code, Azure DevOps, Azure Kubernetes Service and more.

All of these new features and capabilities will help developers create more powerful applications with less effort. With the new features and capabilities of .NET 7, developers can now create applications that are secure, scalable, and robust.

For those who missed the .NET Conf 2022 in November, I wrote an article summarizing the event in a nutshell.

.NET 7 Features – What’s New in .NET 7

After the official release of .NET 6, Microsoft immediately started working on the new version of .NET 7. In these months it has been publishing and releasing the upcoming features of .NET 7.

In this article we have compiled all the new .NET 7 features based on Microsoft Docs and their release notes. Discover all .NET 7 features!

Faster, Lighter Apps (Native AOT)

Native AOT (Ahead-of-time) is another of the new improvements and novelties that Microsoft brings this time in .NET 7. After a while, the experimental Native AOT project has become the main focus of Microsoft’s development. As many of us have been asking for a long time, Microsoft has decided to bring us a couple of updates to Native AOT.

For those who don’t know what Native AOT is, Ahead-of-time (simply AOT) generates code at compile-time instead of run-time.

At this time, Microsoft already offers ReadyToRun, also called RTR (client/server applications) and Mono AOT (mobile and WASM applications) for this purpose. In addition, Microsoft adds that Native AOT does not replace Mono AOT or WASM.

Native AOT is characterized by what its name indicates: It generates code at compile time but in Native. Its biggest advantage is the performance improvement, according to Microsoft mainly in:

  • Startup time
  • Memory usage
  • Disk size
  • Access to restricted platforms

Microsoft explains how Native AOT works:

“Applications start running the moment the operating system pages in them into memory. The data structures are optimized for running AOT generated code, not for compiling new code at runtime. This is similar to how languages like Go, Swift, and Rust compile. Native AOT is best suited for environments where startup time matters the most.”

In addition, they have revealed a benchmark comparing NativeAOT against ReadyToRun, in which the compile time is up to 73% faster and almost half as light:

image 74
Speed comparison (Source: Microsoft)
image 75
Size comparison (Source: Microsoft)

System.Composition.Hosting

Apart from Native AOT, we have many more new features such as the Managed Extensibility Framework update: Now the new APIs will allow you to add a single object instance to the System.Composition.Hosting container:

namespace System.Composition.Hosting
{
    public class ContainerConfiguration
    {
        public ContainerConfiguration WithExport<TExport>(TExport exportedInstance);
        public ContainerConfiguration WithExport<TExport>(TExport exportedInstance, string contractName = null, IDictionary<string, object> metadata = null);

        public ContainerConfiguration WithExport(Type contractType, object exportedInstance);
        public ContainerConfiguration WithExport(Type contractType, object exportedInstance, string contractName = null, IDictionary<string, object> metadata = null);
    }
}

You can check the original proposal: Inject existing object into MEF2

Observability

Microsoft also brings us improvements in support for the cloud-native specification (OpenTelemetry). Although it is still under development in .NET 7, it has been added Allow samplers to modify tracestate and Samplers should be allowed to modify tracestate. Here we can see the Microsoft example:

//  ActivityListener Sampling callback
    listener.Sample = (ref ActivityCreationOptions<ActivityContext> activityOptions) =>
    {
        activityOptions = activityOptions with { TraceState = "rojo=00f067aa0ba902b7" };
        return ActivitySamplingResult.AllDataAndRecorded;
    };

Reduced start-up time (Write-Xor-Execute)

As we have already seen at the beginning, Microsoft has decided to focus mainly on performance improvements and this one is no exception. Now with Reimplement stubs to improve performance we have seen an improvement in startup time that, according to Microsoft, is up to 10–15%.

This is mainly due to a large reduction in the number of modifications after code creation at runtime.

Let’s check the benchamarks:

image 76
Win x64 Start-Up time Benchmark (Source: Github)
image 77
Linux Intel x64 Start-Up time Benchmark (Source: Github)
image 78
Linux AMD x64 Start-Up time Benchmark (Source: Github)

Generating X.500 names more robustly

Also in this preview, Microsoft has focused on cryptographic security. That is why building an X500DistinguishedName is now much easier and more secure.

For those who don’t know this about the construction of an X.500 name, it used to be done with string manipulation (simple literal or string formatted). This way:

request = new CertificateRequest($"CN={subjectName},OU=Test,O=""Fabrikam, Inc.""", ...);

The main problem with this is that the quote, comma or any other element influences the parser. Microsoft’s solution has been to add the X500DistinguishedName class. There would be no problem since each method can only operate on a single RDN (Relative Distinguished Name).

Let’s look at the Microsoft example:

X500DistinguishedNameBuilder nameBuilder = new();
nameBuilder.AddCommonName(subjectName);
nameBuilder.AddOrganizationalUnitName("Test");
nameBuilder.AddOrganizationName("Fabrikam, Inc.");

request = new CertificateRequest(nameBuilder.Build(), ...);

These are the main new features of the new .NET 7 preview 3. If you want to see in depth everything that brings in its latest version, I recommend you the original source: Announcing .NET 7 Preview 3.

The union of Blazor and .NET MAUI

As we already know, Blazor is a web application development framework and one of its main features is that Blazor has the ability to run the application views on the client side — and not on the server and then teach the browser the HTML.

🧠Interesting fact: In case you didn’t know, the name Blazor comes from the union of two words: Browser and Razor — the .NET HTML view generation engine.

And as we also learned in a previous article, .NET MAUI is the Framework Microsoft is working on that focuses on mobile applications. And if you’re asking yourself… Yes, many people say that .NET MAUI will be the evolution of Xamarin.

This is where Microsoft is going to do a “milkshake” with Blazor and .NET MAUI that will become Blazor Hybrid. Let’s take a closer look.

Blazor Hybrid: The revolution?

In order to explain it in the best possible way, we must go a little bit to the past (specifically 2018), which was the year of the initial release of Blazor (web). At that time it was only possible to build web pages with HTML, CSS and also with C#. This was unfeasible to think about several years ago, but now Microsoft wants to go a step further — and strong: Now, they are working on bringing together the main features and functions of Blazor and .NET MAUI to Blazor Hybrid.

As Visual Studio Magazine explains,

Blazor Hybrid apps are native apps that can leverage web technologies like HTML and CSS for additional functionality. Thus a hybrid app might use an embedded WebView control to render web UI while also leveraging native device capabilities.

image 79
Native UI & Web UI (source: Microsoft)

Cloud Native and containers

Another new feature explained by Microsoft is the Modern Cloud. As we already know, cloud-native applications are usually built from scratch to take advantage of all the resources of containers and databases. This architecture is very good because it allows scaling applications very easily with the creation of autonomous subsystems. This, in turn, reduces costs in the long run.

What .NET will bring will be the facilitation of the creation of cloud-native applications with different kinds of improvements in the developer experience — thanks Microsoft for thinking of us.

Besides, it will simplify quite a lot the installation and configuration of authentication and authorization systems. And of course, as always, small performance improvements at application runtime.

Easier upgrading .NET applications

As we all know — and some have suffered through it, migrating older applications to .NET 6 has not been the easiest thing in the world. That’s why Microsoft is bringing new upgrade enhancements for older applications. The main points are:

  • More code analyzers
  • More code checkers
  • Compatibility checkers

All of this will come along with the .NET Upgrade Assistant that will greatly help in getting those applications upgraded, saving the developer time — thanks Microsoft.

Upgraded Hot Reload

My favorite feature of .NET 6 will be updated in .NET 7. C# Hot Reload feature will be present in Blazor WebAssembly and .NET for iOS and Android.

In addition, Microsoft includes new features such as:

  • Adding static lambdas to existing methods
  • Adding lambdas that capture this to existing methods that already have at least one lambda that captures this
  • Adding new static or non-virtual instance methods to existing classes
  • Adding new static fields to existing classes
  • Adding new classes

New APIs and improvements

System.Text.Json includes a few of small quality-of-life improvements:

  • Include a JsonSerializerOptions
  • Include a JsonWriterOptions.MaxDeph property, and verify that this value is derived from the corresponding
  • Patch methods added to System.Net.Http.Json

In previous versions this was not possible but thanks to these additions to System.Text.Json , serialization and deserialization of polymorphic type hierarchies is now possible.

Let’s look at the Microsoft example:

[JsonDerivedType(typeof(Derived))]
public class Base
{
    public int X { get; set; }
}

public class Derived : Base
{
    public int Y { get; set; }
}

.NET 7 Serialization

Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "X" : 0, "Y" : 0 }

.NET 7 Deserialization

Base value = JsonSerializer.Deserialize<Base>(@"{ ""X"" : 0, ""Y"" : 0 }");
value is Derived; // false

In this case Eirik Tsarpalis states in his proposal that a type discriminator must be specified to enable polymorphic deserialization. This is the way he shows it:

[JsonDerivedType(typeof(Base), typeDiscriminator: "base")]
[JsonDerivedType(typeof(Derived), typeDiscriminator: "derived")]
public class Base
{
    public int X { get; set; }
}

public class Derived : Base
{
    public int Y { get; set; }
}

📚 Note: If you wish to consult more information about the serialization and deserialization, I recommend you take a look at the original proposal: Dotnet Issue: #63747

Activity.Current New Standard

Currently in .NET 6, to achieve span context tracking of the different threads being managed, the most common is to use AsyncLocal<T> for this purpose.

According to Jeremy Likness in his Announcing .NET 7 Preview 4 post: 

“…with Activity becoming the standard to represent spans, as used by OpenTelemetry, it is impossible to set the value changed handler since the context is tracked via Activity.Current.”

Now with Activity.CurrentChanged we will be able to achieve this to receive notifications. Let’s see Microsoft example:

public partial class Activity : IDisposable
    {
        public static event EventHandler<ActivityChangedEventArgs>? CurrentChanged;
    }

And this is how it would be used:

Activity.CurrentChanged += CurrentChanged;

    void CurrentChanged(object? sender, ActivityChangedEventArgs e)
    {
        Console.WriteLine($"Activity.Current value changed from Activity: {e.Previous.OperationName} to Activity: {e.Current.OperationName}");
    }

📚 For more in-depth details, I recomend you to check the original proposal: Api handle Activity.Current value changes

Exposed Methods in performance-critical scenarios

The main problem this new feature solves, as Mikel Blanchard relates, is that performance tests show many allocations incurred when using enumeration interfaces.

This can now be solved by using exposed methods to enumerate properties with quick access to the elements and with no extra allocations. 

Let’s see Microsoft example:

namespace System.Diagnostics
{
    partial class Activity
    {
        public Enumerator<KeyValuePair<string,object>> EnumerateTagObjects();
        public Enumerator<ActivityLink> EnumerateLinks();
        public Enumerator<ActivityEvent> EnumerateEvents();

        public struct Enumerator<T>
        {
            public readonly Enumerator<T> GetEnumerator();
            public readonly ref T Current;
            public bool MoveNext();
        }
    }
}

And this is how it would be used:

Activity a = new Activity("Root");

    a.SetTag("key1", "value1");
    a.SetTag("key2", "value2");

    foreach (ref readonly KeyValuePair<string, object?> tag in a.EnumerateTagObjects())
    {
        Console.WriteLine($"{tag.Key}, {tag.Value}");
    }

📚 For more in-depth details, I recomend you to check the original proposal: System.Diagnostics.Activity: Enumeration API

Microseconds and Nanoseconds in date/time structures

The smallest time increment that could be used was the “tick” and its value is 100ns. The problem with this is that to determine a value in microseconds or nanoseconds you had to calculate everything based on the “tick” and this was not the most optimal thing in the world.

As Microsoft reports, they will now add microsecond and nanosecond values to the different date and time structures that exist.

Let’s see Microsoft example:

.NET 7 DateTime

namespace System {
    public struct DateTime {
        public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);
        public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.DateTimeKind kind);
        public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.Globalization.Calendar calendar);
        public int Microsecond { get; }
        public int Nanosecond { get; }
        public DateTime AddMicroseconds(double value);
    }
}

.NET 7 TimeOnly

namespace System {
    public struct TimeOnly {
        public TimeOnly(int hour, int minute, int second, int millisecond, int microsecond);
        public int Microsecond { get; }
        public int Nanosecond { get; }
    }
}

📚 For more in-depth details, I recomend you to check the original proposal: Add Microseconds and Nanoseconds to TimeStamp, DateTime, DateTimeOffset, and TimeOnly

Single Memory Cache

Now you can instantiate a single memory cache with the AddMemoryCache API. In addition, you will be able to get it injected so you can call GetCurrentStatistics . Let’s check Microsoft example:

// when using `services.AddMemoryCache(options => options.TrackStatistics = true);` to instantiate

    [EventSource(Name = "Microsoft-Extensions-Caching-Memory")]
    internal sealed class CachingEventSource : EventSource
    {
        public CachingEventSource(IMemoryCache memoryCache) { _memoryCache = memoryCache; }
        protected override void OnEventCommand(EventCommandEventArgs command)
        {
            if (command.Command == EventCommand.Enable)
            {
                if (_cacheHitsCounter == null)
                {
                    _cacheHitsCounter = new PollingCounter("cache-hits", this, () =>
                        _memoryCache.GetCurrentStatistics().CacheHits)
                    {
                        DisplayName = "Cache hits",
                    };
                }
            }
        }
    }

In addition, Microsoft leaves an example of how it would help us to see stats with the dotnet-counters tool (check it here):

Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    CPU Usage (%)                                      0
    Working Set (MB)                                  28
[Microsoft-Extensions-Caching-MemoryCache]
    cache-hits                                       269

📚 For more in-depth details, I recomend you to check the original proposal: Let consumers of MemoryCache access metrics

Multiple Memory Cache

As in the previous feature, which allowed instantiating a single cache memory, we can also instantiate multiple memory cache with GetCurrentStatistics . Let’s check this Microsoft example:

static Meter s_meter = new Meter("Microsoft.Extensions.Caching.Memory.MemoryCache", "1.0.0");
static IMemoryCache? mc1;
static IMemoryCache? mc2;

static void Main(string[] args)
{
   s_meter.CreateObservableGauge<long>("cache-hits", GetCacheHits);
   mc1 = new MemoryCache(new MemoryCacheOptions() { TrackStatistics = true, SizeLimit = 30 });
   mc2 = new MemoryCache(new MemoryCacheOptions() { TrackStatistics = true, SizeLimit = 30 });

   // call to: mc1.TryGetValue(key1, out object? value)
   // or: mc2.TryGetValue(key2, out value2)
   // increments TotalHits
}

// metrics callback for cache hits
static IEnumerable<Measurement<long>> GetCacheHits()
{
   return new Measurement<long>[]
   {
      new Measurement<long>(mc1!.GetCurrentStatistics()!.TotalHits, new KeyValuePair<string,object?>("CacheName", "mc1")),
      new Measurement<long>(mc2!.GetCurrentStatistics()!.TotalHits, new KeyValuePair<string,object?>("CacheName", "mc2")),
   };

And also, as in the previous feature, Microsoft shows us that we can also measure stats with the dotnet-counters tool (check it here): 

Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    CPU Usage (%)                                      0
    Working Set (MB)                                  14
[Microsoft.Extensions.Caching.Memory.MemoryCache]
    cache-hits
        CacheName=mc1                             13,204
        CacheName=mc2                             13,204

📚 For more in-depth details, I recomend you to check the original proposal: Let consumers of MemoryCache access metrics

New Tar APIs

We will now have cross-platform APIS with which we can extract and modify (read and write) tar archives. As usual, Microsoft has shown examples so let’s take a look at some of them:

.NET 7 Archive Tar API

// Generates a tar archive where all the entry names are prefixed by the root directory 'SourceDirectory'
TarFile.CreateFromDirectory(sourceDirectoryName: "/home/dotnet/SourceDirectory/", destinationFileName: "/home/dotnet/destination.tar", includeBaseDirectory: true);

.NET 7 Extract Tar API

// Extracts the contents of a tar archive into the specified directory, but avoids overwriting anything found inside
TarFile.ExtractToDirectory(sourceFileName: "/home/dotnet/destination.tar", destinationDirectoryName: "/home/dotnet/DestinationDirectory/", overwriteFiles: false);

📚 For more in-depth details, I recomend you to check the original proposal: Implement Tar APIs

OSR (On Stack Replacement)

OSR (On Stack Replacement) is a great complement to tiered compilation. It allows, in the middle of the execution of a method, to change the code that is being executed by the methods that are being executed at the moment.

According to Microsoft:

“OSR allows long-running methods to switch to more optimized versions mid-execution, so the runtime can jit all methods quickly at first and then transition to more optimized versions when those methods are called frequently (via tiered compilation) or have long-running loops (via OSR).”

With OSR, we can obtain up to 25% extra speed at start-up (Avalonia IL Spy test) and according to TechEmpower, improvements can range from 10% to 30%.

image 80
Performance Impact (Source: Microsoft)

📚 If you want to know in how OSR works, please refer: OSR Document

CopyString UTF-8 & UTF-16

New methods are now included to be able to consume decoded JSON strings. Previously this was only possible using Utf8JsonReader.GetString() but now with CopyString you can copy UTF-8 strings or even UTF-16 strings without decoding.

Microsoft leaves this example:

int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.Slice(0, charsRead);

ParseUnescapedString(source); // handle the unescaped JSON string
ArrayPool<char>.Shared.Return(buffer);

📚 Check original source: Dotnet Issue: #54410

Analyzer

We now have a new analyzer. This analyzer is in charge of looking for the Regex uses with the possibility of conversion for the use of the RegexGenerator source generator:

image 81
Analyzer (Source: Microsoft)

According to Microsoft, the generator output depends on the known values of the parameters supplied:

“They are part of an app that targets .NET 7. The new analyzer ships inside the .NET 7 targeting pack and only apps targeting .NET 7 are eligible for this analyzer.
The LangVersion is higher than 10. For the time being the regex source generator requires LangVersion to be set to preview.”

📚 Check original source: Analyzer

Code fixer

Of course, if you have an Analyzer, you can’t miss a Code Fixer. Well, here it is, Microsoft tells us that the first of its two functions (for the moment, let’s keep waiting for more news) is in charge of suggesting RegexGenerator source generator methods with the possibility of overriding the name that comes by default

The second function of this code fixer that comes in .NET 7 is that, through a call to the new method, it replaces the original code.

Let’s see how the code fixer looks like in the Microsoft example:

image 82
Code fixer (Source: Microsoft)

📚 Check original source: Code Fixer

Improved Regex source generator

The new Regex Source Generator means that you can shave off up to 5x the time spent optimizing patterns from our compiled engine without having to sacrifice any of the associated performance benefits.

Additionally, because it operates within a partial class, there is no overhead of compiling-time for instances when users know their pattern at runtime. With this feature, if your pattern can be known at compile-time, then this generator is recommended instead of our traditionally compiler based approach.

All you need to do is set up a partial declaration with an attribute called RegexGenerator which points back to the method that will return an already precompiled regular expression object (with all other features enabled). Our generator will take care of creating that method and updating it as needed according to changes made to either the original string or passed in options (like case sensitivity etc…).

Let’s take a look at the comparison of the Microsoft example:

.NET 6 Regex source generator

public class Foo
{
  public Regex regex = new Regex(@"abc|def", RegexOptions.IgnoreCase);

  public bool Bar(string input)
  {
    bool isMatch = regex.IsMatch(input);
    // ..
  }
}

.NET 7 Regex source generator

public partial class Foo  // <-- Make the class a partial class
{
   [RegexGenerator(@"abc|def", RegexOptions.IgnoreCase)] // <-- Add the RegexGenerator attribute and pass in your pattern and options
  
   public static partial Regex MyRegex(); //  <-- Declare the partial method, which will be implemented by the source generator
  public bool Bar(string input)
  {
    bool isMatch = MyRegex().IsMatch(input); // <-- Use the generated engine by invoking the partial method.
    // ..
  }
}

SDK improvements

For those who work with .NET Framework, using dotnet newwill be a much easier task. With major updates such as an improved intuitiveness and an increased speed of tab completion, it would be hard to find anything negative about this change.

If you want to go deeper, here is the original example from Microsoft:

dotnet new --help
Description:
  Template Instantiation Commands for .NET CLI.

Usage:
  dotnet new [<template-short-name> [<template-args>...]] [options]
  dotnet new [command] [options]

Arguments:
  <template-short-name>  A short name of the template to create.
  <template-args>        Template specific options to use.

Options:
  -?, -h, --help  Show command line help.

Commands:
  install <package>       Installs a template package.
  uninstall <package>     Uninstalls a template package.
  update                  Checks the currently installed template packages for update, and install the updates.
  search <template-name>  Searches for the templates on NuGet.org.
  list <template-name>    Lists templates containing the specified template name. If no name is specified, lists all templates.

New command names

Command lines in general were changing — specifically, every command shown in this output will no longer include the --prefix as it does now. This was done to align with what a user would expect from a subcommand for an app built for command lines.

The old versions of these commands (--install, etc) are still available just in case it breaks scripts but we hope that one day there will be deprecation warnings included to those commands so you can transition over without risk.

Tab key completion

For a long time now, the dotnet command line interface has had support for tab completion on shells such as PowerShell, bash, zsh, and fish. The commands themselves are able to decide what they want to show when given input.

Now in .NET 7, the newcommand allows quite a lot of functionalities:

dotnet new angular
angular              grpc                 razor                viewstart            worker               -h
blazorserver         mstest               razorclasslib
web                  wpf                  /?
blazorwasm           mvc                  razorcomponent       webapi               wpfcustomcontrollib  /h
classlib             nugetconfig          react                webapp               wpflib               install
console              nunit                reactredux           webconfig            wpfusercontrollib    list
editorconfig         nunit-test           sln                  winforms             xunit                search
gitignore            page                 tool-manifest        winformscontrollib   --help               uninstall
globaljson           proto                viewimports          winformslib          -?                   update

If you are interested in knowing how to enable it, I recommend you to check the Microsoft’s guide. And likewise, if you want to know all the possibilities of this feature, I recommend you to consult again the original source.

Dynamic PGO improvements

Microsoft has recently introduced a new breakthrough for program optimization. Called the Dynamic PGO, it is designed to make some key changes from the Static PGO we already know about. Where Static PGO requires developers to use special tools separately from training, Dynamic PGO doesn’t require any of that; all you need to do is run the application you are interested in optimizing and then collect data for Microsoft!

Now, according with Andy Ayers in GitHub, the following is added:

“Extend ref counting done by local morph so that we can determine
single-def single-use locals.”

“Add a phase that runs just after local morph that will attempt to
forward single-def single-use local defs to uses when they are in
adjacent statements.”

Fix or work around issues uncovered elsewhere:

  • gtFoldExprCompare might fold “identical” volatile subtrees
  • fgGetStubAddrArg cannot handle complex trees
  • some simd/hw operations can lose struct handles
  • some calls cannot handle struct local args

You can check the original pull for more details.

Improved System.Reflection Performance

Starting with the performance enhancements of the new .NET 7 features, we have first the enhancement of the System.Reflection namespace.The System.Reflection namespace is responsible for containing and storing types by metadata, to facilitate the retrieval of stored information from modules, members, assemblies and more.

They are mostly used for manipulating instances of loaded types and with them you can create types dynamically in a simple way.

With this update by Microsoft, now in .NET 7 the overhead when invoking a member using reflection has been reduced considerably. If we talk about numbers, according to the last benchmark provided by Microsoft (made with the package BenchmarkDotNet ) you can see up to 3–4x faster.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;

namespace ReflectionBenchmarks
{
    internal class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<InvokeTest>();
        }
    }

    public class InvokeTest
    {
        private MethodInfo? _method;
        private object[] _args = new object[1] { 42 };

        [GlobalSetup]
        public void Setup()
        {
            _method = typeof(InvokeTest).GetMethod(nameof(InvokeMe), BindingFlags.Public | BindingFlags.Static)!;
        }

        [Benchmark]
        // *** This went from ~116ns to ~39ns or 3x (66%) faster.***
        public void InvokeSimpleMethod() => _method!.Invoke(obj: null, new object[] { 42 });

        [Benchmark]
        // *** This went from ~106ns to ~26ns or 4x (75%) faster. ***
        public void InvokeSimpleMethodWithCachedArgs() => _method!.Invoke(obj: null, _args);

        public static int InvokeMe(int i) => i;
    }
}

Loop Optimizations

This improvement is not very impressive but it still manages to improve performance significantly. This time Microsoft has decided to proceed with the elimination of the initialization conditions of the loop cloning variables:

According to Bruce Forstall in his pr:

“Assume that any pre-existing initialization is acceptable”

“Check condition against zero if necessary. Const inits remain as before”

These improvements allow a 21% performance increase with System.Collections.Tests.Perf_BitArrayLeftShift (Size:512) . Let’s see the benchmark provided by Microsoft:

image 83
System.Collections.Tests.Perf_BitArray.BitArrayLeftShift Benchmark (Source: Microsoft)

Critical Performance Tags — 83% Faster

Microsoft tells us that it is now possible to expose tag enumerator methods. This is especially intended for critical scenarios where performance is of vital importance and can now be achieved using ActivityEvent and ActivityLink.

This is an example of use that Microsoft gives us:

var tags = new List<KeyValuePair<string, object?>>()
{
    new KeyValuePair<string, object?>("tag1", "value1"),
    new KeyValuePair<string, object?>("tag2", "value2"),
};

ActivityLink link = new ActivityLink(default, new ActivityTagsCollection(tags));

foreach (ref readonly KeyValuePair<string, object?> tag in link.EnumerateTagObjects())
{
    // Consume the link tags without any extra allocations or value copying.
}            

ActivityEvent e = new ActivityEvent("SomeEvent", tags: new ActivityTagsCollection(tags));

foreach (ref readonly KeyValuePair<string, object?> tag in e.EnumerateTagObjects())
{
    // Consume the event's tags without any extra allocations or value copying.
}

In this way, object enumerations can be performed with no loss of speed or performance and with easy and fast access to the elements.

Moreover, Mikel Blanchard — the author of this proposal, adds a test comparing 3 scenarios in which he clearly shows that this new feature increases performance exponentially and in turn reduces error time.

First of all Mikel explains that enumerating is much more demanding to enumerate using that API than using Enumerate* API:

public IEnumerable<KeyValuePair<string, object?>>? Tags { get; }

And the results were as follows:

image 84
API Proposal #68056 (By: Mikel Blanchard)

In the next test, Mikel improved performance by 71% (although he considered it “also very slow).

In this case it was performed with:

foreach (KeyValuePair<string, object> tag in (ActivityTagsCollection)activityEvents.Tags

And this was reflected in a 71% improvement in performance:

image 85
API Proposal #68056 (By: Mikel Blanchard)

After that, he decided to perform the same test but enumerating over an array:

private struct Enumerator
    {
        private readonly KeyValuePair<string, object>[] source;
        private int index;

        public Enumerator(KeyValuePair<string, object>[] source)
        {
            this.source = source;
            this.index = -1;
        }

        public readonly KeyValuePair<string, object> Current => this.source[this.index];

        public readonly Enumerator GetEnumerator() => this;

        public bool MoveNext()
        {
            return ++this.index < this.source.Length;
        }
    }

And the result was 83% more performance compared to the previous test performed by Mikel:

image 86
API Proposal #68056 (By: Mikel Blanchard)

📚 Note: If you wish to consult more information about the tests, I recommend you take a look at the original proposal: API Proposal: #68056

.NET 7 Release Candidate Features

Microsoft is currently releasing the Official Release Candidates of .NET 7. Each and every one of them with new and unique features while being covered by the Release Candidate Policy, all applications in production are officially supported by Microsoft. Discover the latest enhancements and new features of .NET 7!

Fill out my online form.

Leave a Reply