Announcing .Net seven Preview 4

Today we released .NET vii Preview 4. The fourth preview of .Net 7 includes enhancements to observability in the .Cyberspace implementation of OpenTelemetry, the addition of properties to track microseconds and nanoseconds in date and time structures, new metrics for caching extensions, performance-boosting "on stack replacement," APIs to work with .tar athenaeum, and boosted features as part of an ongoing endeavor to improve the operation of and add together features to regular expressions in .Cyberspace seven. The preview 4 releases for ASP.Internet Core and EF7 are also available.

Yous can download .Net 7 Preview 4, for Windows, macOS, and Linux.

  • Installers and binaries
  • Container images
  • Linux packages
  • Release notes
  • Known problems
  • GitHub effect tracker

.NET 7 Preview iv has been tested with Visual Studio 17.3 Preview 1. We recommend you use the preview channel builds if you want to try .NET 7 with Visual Studio family products. Visual Studio for Mac back up for .NET 7 previews isn't available withal simply is coming soon. Now, let'due south get into some of the latest updates in this release.

.Cyberspace Libraries: Nullable annotations for Microsoft.Extensions

#43605

We have finished annotating the Microsoft.Extensions.* libraries for nullability. In .NET vii Preview four, all Microsoft.Extensions.* libraries have been fully annotated for nullability.

This work wouldn't have been possible without @maxkoshevoi'south multiple-calendar month endeavour. Starting with the first PR in August 2021, all the way to the final PR in April 2022, this was a lot of work that is profoundly appreciated by the .NET community.

Information technology's a actually great feeling to know that if you don't similar something about libraries you are using (or even the framework itself), you can get and improve it yourself. – Maksym

Contributor spotlight: Maksym Koshovyi

Microsoft's Eric Erhardt reached out to Maksym to give thanks him for his contributions and acquire more nearly his passion for open source software and .Cyberspace. We are grateful for his contributions and would like to highlight his story. In Maksym's own words:

Image mkoshovyi jpg

My name is Maksym Koshovyi, and I am from Kharkiv, Ukraine.

I've loved programming since school and been doing it for over viii years (past 4 even got paid to do it 😄). Learned C# early on and never switched. Such a smashing language, framework and community! Always loved how piece of cake it is to throw together a WinForms app in a couple of hours, or an API, or even a mobile app (without ever switching to Java or Swift). Also always enjoyed the language itself and the tooling around it that makes information technology piece of cake to fall into the pit of success.

Ironically, my commencement contribution e'er was a Roslyn analyzer. I got so used to being warned if I do stupid things, that when it didn't happen I've defended a 24-hour interval to fixing information technology. Since and so I've made many pocket-size contributions here or in that location. Two of the biggest ones for now are in XamarinCommunityToolkit and runtime. Information technology's a really great feeling to know that if yous don't like something nearly libraries y'all are using (or even the framework itself), you tin become and meliorate it yourself. Guess it's something similar to driving a car you lot know how to disassemble and fix yourself.

What'south likewise slap-up about contributing is that you lot learn so much! All projects are different, and having more than perspectives on how things could be done always helps. I ever find myself implementing something I've found on GitHub in my pet projects. Another matter is feeling that your contribution will make someone else'due south life a bit easier. Always feels good thinking about it.

Simply none of this would be possible without the team that encourages contributions, discusses the issues and helps with PR reviews. Thanks so much for creating such a welcoming and open surround!

You're very welcome, Maksym! Thank YOU! – .NET Squad

Observability

The following enhancements were made equally part of the ongoing effort to support observability in .Internet seven via OpenTelemetry.

Introducing Activity.Current change effect

#67276

A typical implementation of distributed tracing uses an AsyncLocal<T> to rails the "span context" of managed threads. Changes to the span context are tracked by using the AsyncLocal<T> constructor that takes the valueChangedHandler parameter. However, with Action becoming the standard to stand for spans, as used by OpenTelemetry, information technology is impossible to set the value inverse handler since the context is tracked via Activity.Current. The new change outcome can be used instead to receive the desired notifications.

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

Usage Instance

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

Expose performant Activity backdrop enumerator methods

#67207

The exposed methods can exist used in the performance-critical scenarios to enumerate the Activeness Tags, Links, and Events backdrop without any extra allocations and with fast items access.

          namespace Organization.Diagnostics {     partial class Activity     {         public Enumerator<KeyValuePair<cord,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();         }     } }        

Usage Example

                      Activeness a = new Activity("Root");      a.SetTag("key1", "value1");     a.SetTag("key2", "value2");      foreach (ref readonly KeyValuePair<string, object?> tag in a.EnumerateTagObjects())     {         Panel.WriteLine($"{tag.Central}, {tag.Value}");     }        

Adding Microseconds and Nanoseconds to TimeStamp, DateTime, DateTimeOffset, and TimeOnly

#23799

Prior to Preview 4, the lowest increment of fourth dimension bachelor in the diverse date and time structures was the "tick" available in the Ticks property. In .NET, a unmarried tick is 100ns. Developers traditionally have had to perform computations on the "tick" value to determine microsecond and nanosecond values. Preview iv addresses that by introducing both microseconds and milliseconds to the date and time implementations. Hither is the new API surface expanse:

          namespace System {     public struct DateTime {         public DateTime(int twelvemonth, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);         public DateTime(int year, int month, int mean solar day, int hour, int minute, int second, int millisecond, int microsecond, System.DateTimeKind kind);         public DateTime(int twelvemonth, int month, int twenty-four hours, int hour, int minute, int 2d, int millisecond, int microsecond, System.Globalization.Agenda agenda);         public int Microsecond { go; }         public int Nanosecond { get; }         public DateTime AddMicroseconds(double value);     }     public struct DateTimeOffset {         public DateTimeOffset(int twelvemonth, int calendar month, int 24-hour interval, int 60 minutes, int minute, int 2d, int millisecond, int microsecond, Arrangement.TimeSpan commencement);         public DateTimeOffset(int year, int month, int day, int 60 minutes, int minute, int 2d, int millisecond, int microsecond, System.TimeSpan start, System.Globalization.Calendar calendar);         public int Microsecond { get; }         public int Nanosecond { go; }         public DateTimeOffset AddMicroseconds(double microseconds);     }     public struct TimeSpan {         public const long TicksPerMicrosecond = 10L;         public const long NanosecondsPerTick = 100L;         public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds, int microseconds);         public int Microseconds { get; }         public int Nanoseconds { get; }         public double TotalMicroseconds { become; }         public double TotalNanoseconds { get; }         public static TimeSpan FromMicroseconds(double microseconds);     }     public struct TimeOnly {         public TimeOnly(int hour, int minute, int second, int millisecond, int microsecond);         public int Microsecond { get; }         public int Nanosecond { become; }     } }        

Thanks to @ChristopherHaws and @deeprobin helping in the design and implementation.

More improvements and new APIs for System.Text.RegularExpressions

For preview 4 we are calculation the remaining planned APIs in lodge to add together span support into our Regex library. The changes span several bug:

  • Augment Regex extensibility point for amend perf and span-based matching
  • [API Proposal]: Add Regex.Enumerate(ReadOnlySpan) which is resource allotment free
  • Overhaul Regex'due south handling of RegexOptions.IgnoreCase

These APIs are amortized allocation-free. The principal span-based APIs beingness added for Preview 4 are:

  • Regex.IsMatch(ReadOnlySpan<char> input): Indicates whether the regular expression finds a match in the input span.
  • Regex.Count(ReadOnlySpan<char> input): Searches an input string for all occurrences of a regular expression and returns the number of matches.
  • Regex.EnumerateMatches(ReadOnlySpan<char> input): Searches an input span for all occurrences of a regular expression and returns a ValueMatchEnumerator to lazily iterate over the matches.

We accept also washed a lot of work that enhances Regex performance in general, with improvements like:

  • Improving the handling of more common Regex sets
  • Improving the performance in the logic that finds possible positions where a match could exist.
  • Use spans in some of our internal types to avoid allocations where possible, which makes the engine go faster.
  • Improve the logic for when a loop can be made atomic.

Finally, we have also made some improvements on the code that is generated by the Regex source generator to make the code more than readable and easier to debug, likewise as enable projects with multiple source generated regex patterns to share common lawmaking betwixt them.

Added metrics for Microsoft.Extensions.Caching

#50406

For preview 4 we added metrics support for IMemoryCache. The main APIs being added for Preview 4 are:

  • MemoryCacheStatistics which holds cache hit/miss/estimated size and count for IMemoryCache
  • GetCurrentStatistics: returns an case of MemoryCacheStatistics, or null when TrackStatistics flag is not enabled. The library has a built-in implementation available for MemoryCache.

GetCurrentStatistics() API allows app developers to use either upshot counters or metrics APIs to track statistics for one or more retentivity cache. Note, using these APIs for getting statistics for multiple caches is possible simply requires developers to write their own meter.

Upshot #67770 tracks calculation built-in meters into the library to help ameliorate developer experience with monitoring caches.

How to use the APIs

GetCurrentStatistics() API (based on #50406) allows app developers to employ either outcome counters or metrics APIs to track statistics for ane or more retentiveness caches with lawmaking snippets.

With IMemoryCache.GetCurrentStatistics(), the user now has support for the post-obit use cases:

  • 1 cache with either consequence counters or metrics APIs
  • Multiple caches with metrics API

Using IMemoryCache.GetCurrentStatistics() for i retentiveness cache

Use the AddMemoryCache API to instantiate a single retention enshroud and via DI get it injected to enable them calling GetCurrentStatistics.

Sample usage/screenshot for effect counter:

          // when using `services.AddMemoryCache(options => options.TrackStatistics = true);` to instantiate      [EventSource(Name = "Microsoft-Extensions-Caching-Memory")]     internal sealed grade CachingEventSource : EventSource     {         public CachingEventSource(IMemoryCache memoryCache) { _memoryCache = memoryCache; }         protected override void OnEventCommand(EventCommandEventArgs command)         {             if (command.Command == EventCommand.Enable)             {                 if (_cacheHitsCounter == cipher)                 {                     _cacheHitsCounter = new PollingCounter("enshroud-hits", this, () =>                         _memoryCache.GetCurrentStatistics().CacheHits)                     {                         DisplayName = "Cache hits",                     };                 }             }         }     }        

Helps them view stats below with dotnet-counters tool:

          Press p to intermission, r to resume, q to quit.     Status: Running  [Organization.Runtime]     CPU Usage (%)                                      0     Working Gear up (MB)                                  28 [Microsoft-Extensions-Caching-MemoryCache]     cache-hits                                       269        

Using IMemoryCache.GetCurrentStatistics() for multiple retentiveness caches

In club to get stats for more than one retention cache in the app, the user may use metrics APIs in their own code, and then long as they take a way of distinguishing their caches by name or ID:

sample usage/screenshot for multiple caches using metrics APIs

static Meter s_meter = new Meter("Microsoft.Extensions.Caching.Memory.MemoryCache", "1.0.0"); static IMemoryCache? mc1; static IMemoryCache? mc2;  static void Primary(string[] args) {    s_meter.CreateObservableGauge<long>("enshroud-hits", GetCacheHits);    mc1 = new MemoryCache(new MemoryCacheOptions() { TrackStatistics = true, SizeLimit = 30 });    mc2 = new MemoryCache(new MemoryCacheOptions() { TrackStatistics = true, SizeLimit = xxx });     // 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")),    }; }

Sample stats with dotnet-counters tool:

          Press p to intermission, r to resume, q to quit.     Status: Running  [System.Runtime]     CPU Usage (%)                                      0     Working Ready (MB)                                  fourteen [Microsoft.Extensions.Caching.Memory.MemoryCache]     enshroud-hits         CacheName=mc1                             13,204         CacheName=mc2                             13,204        

Each metrics would need to create its own observable gauge (i for hits, and so misses, etc.) and each callback function for the gauge iterates through list of caches creating measurements.

Added new Tar APIs

Implement Tar APIs – dotnet/runtime#67883

For Preview 4, we added the new System.Formats.Tar assembly, which contains cross-platform APIs that let reading, writing, archiving, and extracting of Tar athenaeum.

Usage examples

For the virtually common usage (extracting and archiving) the following APIs are available:

          // Generates a tar archive where all the entry names are prefixed past the root directory 'SourceDirectory' TarFile.CreateFromDirectory(sourceDirectoryName: "/home/dotnet/SourceDirectory/", destinationFileName: "/home/dotnet/destination.tar", includeBaseDirectory: true);  // Extracts the contents of a tar annal into the specified directory, only avoids overwriting anything found within TarFile.ExtractToDirectory(sourceFileName: "/habitation/dotnet/destination.tar", destinationDirectoryName: "/dwelling house/dotnet/DestinationDirectory/", overwriteFiles: false);        

We besides offering variants that allow extracting from a stream or archiving into a stream:

          // Generates a tar archive where all the entry names are prefixed by the root directory 'SourceDirectory' using MemoryStream archiveStream = new(); TarFile.CreateFromDirectory(sourceDirectoryName: @"D:SourceDirectory", destination: archiveStream, includeBaseDirectory: true);  // Extracts the contents of a stream tar annal into the specified directory, and avoids overwriting anything establish inside TarFile.ExtractToDirectory(source: archiveStream, destinationDirectoryName: @"D:DestinationDirectory", overwriteFiles: false);        

Additionally, the entries of an annal can be traversed i-past-one using the reader:

          // Opens an archive for reading private entries, and closes the archive stream after the reader disposal using FileStream archiveStream = File.OpenRead("/abode/dotnet/SourceDirectory/source.tar"); using TarReader reader = new(archiveStream, leaveOpen: false); TarEntry? entry; while ((entry = reader.GetNextEntry()) != null) {     // Extracts an entry to the desired destination, and overwrites if an entry with the same name is institute     string destinationFileName = Path.Join("/home/dotnet/DestinationDirectory", entry.Name);     entry.ExtractToFile(destinationFileName, overwrite: true); }        

And entries can also be written i-past-one into an archive stream using the writer:

          using FileStream archiveStream = File.OpenWrite(@"D:DestinationDirectorydestination.tar"); // Create the archive in the PAX format and write individual entries, closing the stream afterwards the author disposal using TarWriter writer = new(archiveStream, TarFormat.Pax, leaveOpen: simulated);  // Add an entry from an existing file writer.WriteEntry(fileName: @"D:SourceDirectoryfile.txt", entryName: "file.txt");  // Or write an entry from scratch PaxTarEntry entry = new(entryType: TarEntryType.Directory, entryName: "directory"); author.WriteEntry(entry);        

We can also pair these new APIs with stream-based compression methods, like Organisation.IO.Compression.GZipStream.

Extracting the contents from a compressed tar annal into the filesystem looks like this:

          using FileStream compressedStream = File.OpenRead("/abode/dotnet/SourceDirectory/compressed.tar.gz"); using GZipStream decompressor = new(compressedStream, CompressionMode.Decompress); TarFile.ExtractToDirectory(source: decompressor, destinationDirectoryName: "/home/dotnet/DestinationDirectory/", overwriteFiles: false);        

Reading individual entries from a compressed tar annal:

          using FileStream compressedStream = File.OpenRead(@"D:SourceDirectorycompressed.tar.gz"); using GZipStream decompressor = new(compressedStream, CompressionMode.Decompress); using TarReader reader = new(decompressor, leaveOpen: true); TarEntry? entry;  while ((entry = GetNextEntry(copyData: true)) != null) {     Console.WriteLine($"Entry type: {entry.EntryType}, entry name: {entry.Proper name}"); }        

Writing the contents of a directory into a compressed tar annal:

          using MemoryStream archiveStream = new(); TarFile.CreateFromDirectory(sourceDirectoryName: "/home/dotnet/SourceDirectory/", destination: archiveStream, includeBaseDirectory: truthful); using FileStream compressedStream = File.Create("/home/dotnet/DestinationDirectory/compressed.tar.gz"); using GZipStream compressor = new(compressedStream, CompressionMode.Shrink); archiveStream.Seek(0, SeekOrigin.Begin); archiveStream.CopyTo(compressor);        

Writing private entries into a compressed tar archive:

          using MemoryStream archiveStream = new(); using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: truthful)) {     // Add together an entry from an existing file     author.WriteEntry(fileName: @"D:SourceDirectoryfile.txt", entryName: "file.txt");      // Or write an entry from scratch     PaxTarEntry entry = new(entryType: TarEntryType.Directory, entryName: "directory");     writer.WriteEntry(entry); }  using FileStream compressedStream = File.Create(@"D:DestinationDirectorycompressed.tar.gz"); using GZipStream compressor = new(compressedStream, CompressionMode.Shrink); archiveStream.Seek(0, SeekOrigin.Begin); archiveStream.CopyTo(compressor);        

Notes

  • We are not nevertheless including any async APIs. They will be included in a future preview release.
  • In the Ustar, PAX and GNU formats, we do not all the same back up the UName, GName, DevMajor and DevMinor fields.
  • In the GNU format, nosotros won't support the rare entry types for writing, like sparse files, multi-volume, renamed or symlinked, tape book.

What's new in CodeGen

Many thank you to the JIT community contributors who submitted these important PRs:

@sandreenko: 66983; @SingleAccretion: 66558, 66251, 65803, 64581, 67213, 67208, 67206,67486, 67400; @SkiFoD: 65072.

Other updates to CodeGen include dynamic PGO: 65922, Arm64: 66621, 66407, 66902, 67490, 67384; loop optimizations: 68105; and full general optimizations: 67205, 67395, 68055.

On Stack Replacement (aka OSR)

On Stack Replacement allows the runtime to change the code executed by currently running methods in the centre of method execution, while those methods are agile "on stack." It serves every bit a complement to tiered compilation.

#65675 enabled OSR past default on x64 and Arm64, and enabled quick jitting for methods with loops on those aforementioned platforms.

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

Functioning Touch

OSR improves startup time. Most all methods are now initially jitted past the quick jit. We accept seen 25% improvement in startup fourth dimension in jitting-heavy applications like Avalonia "IL" spy, and the various TechEmpower benchmarks we track evidence x-xxx% improvements in time to outset request (meet chart below: OSR was enabled by default on March xxx).

Performance improvements

OSR can also ameliorate functioning of applications, and in item, applications using Dynamic PGO, as methods with loops are now meliorate optimized. For example, the Array2 microbenchmark showed dramatic comeback when OSR was enabled.

OSR improvements in microbenchmark

Farther technical details

Meet the OSR Design Document for details on how OSR works.

Encounter OSR Next Steps for details on the work that went into enabling OSR, and possible hereafter enhancements.

Central Package Management

Dependency direction is a cadre feature of NuGet. Managing dependencies for a single project can be easy. Managing dependencies for multi-project solutions can evidence to be hard equally they start to scale in size and complexity. In situations where you lot manage common dependencies for many unlike projects, y'all can leverage NuGet'due south new primal package management features to do all of this from the ease of a unmarried location.

Targeting .NET 7

To target .NET vii, you need to use a .NET 7 Target Framework Moniker (TFM) in your project file. For case:

          <TargetFramework>net7.0</TargetFramework>        

The full set up of .Cyberspace 7 TFMs, including operating-specific ones follows.

  • net7.0
  • net7.0-windows

We wait that upgrading from .Net 6 to .Cyberspace vii should be straightforward. Please study any breaking changes that you discover in the procedure of testing existing apps with .NET 7.

Support

.NET 7 is a Current release, meaning it will receive free support and patches for xviii months from the release date. It's important to note that the quality of all releases is the same. The only divergence is the length of support. For more near .Cyberspace back up policies, come across the .NET and .NET Core official support policy.

Breaking changes

Y'all can find the well-nigh contempo list of breaking changes in .Net vii by reading the Breaking changes in .NET seven document. It lists breaking changes by area and release with links to detailed explanations.

To see what breaking changes are proposed but withal under review, follow the Proposed .Net Breaking Changes GitHub issue.

Roadmaps

Releases of .Net include products, libraries, runtime, and tooling, and represent a collaboration across multiple teams inside and outside Microsoft. Yous tin learn more than about these areas by reading the product roadmaps:

  • ASP.Net Cadre 7 and Blazor Roadmap
  • EF seven Roadmap
  • ML.NET
  • .NET MAUI
  • WinForms
  • WPF
  • NuGet
  • Roslyn
  • Runtime

Closing

We appreciate and cheers for all your support and contributions to .Internet. Please give .NET 7 Preview iv a try and tell us what yous think!