.NET v7.0 Features Will Change the Way You Code

.NET v7.0 Features Will Change the Way You Code

The.NET 7 framework has recently released some incredible new features that will change the way you code. They haven’t been officially released yet, but you can start playing with them now to prepare for when they do in November!

The latest.NET Framework preview has been made available to the public, and it is full of surprises. Check out the Top 7.NET 7 features that will change the way you code forever!

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.

…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.

Jeremy Likness

Now with Activity.CurrentChanged we will be able to achieve this to receive notifications. Let’s see an 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}");
    }

Exposed Methods in performance-critical scenarios

According to Mikel Blanchard, the main issue this new feature addresses is that performance tests reveal numerous allocations when using enumeration interfaces.

This is now possible by using exposed methods to enumerate properties with quick access to the elements and no additional allocations.

Let’s see an 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}");
    }

Microseconds and Nanoseconds in date/time structures

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

According to Microsoft, they will now add microsecond and nanosecond values to the various date and time structures that exist.

Let’s see an example:

With 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);
    }
}

With 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; }
    }
}

One 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 an 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 us an example of how it would help us to see stats with the dotnet-counters tool

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

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 an 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

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

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:

Archive

// 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);

Extract

// 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);

Share this post

Leave a Reply

Your email address will not be published. Required fields are marked *