DEV Community

Cover image for Hidden NET 9 gems
Karen Payne
Karen Payne

Posted on

Hidden NET 9 gems

Introduction

When Microsoft announces a new version of the NET Framework there are release notes.

The release notes do not cover all new additions and/or features. This article will introduce several new features with C# samples.

Enumerable.Index<T>

Enumerable.Index<T> returns an enumerable that incorporates the element's index into a tuple.

Before working with Index<T>, there is the conventional route which is declaring a int variable with a value of -1 and increment the variable in a foreach.

Note
This is what many seasoned developers use and may very well dislike the new Index extension method as they are set in their ways. There is nothing wrong with this approach.

Example 1

using static System.Globalization.DateTimeFormatInfo;
internal partial class Program
{
    static void Main(string[] args)
    {
        var index = -1;
        foreach (var month in Months)
        {
            ++index;
            Console.WriteLine($"{index,-5}{month}");
        }

        Console.ReadLine();

    }

    public static List<string> Months => CurrentInfo.MonthNames[..^1].ToList();
}
Enter fullscreen mode Exit fullscreen mode

results from above code

Another approach is writing an extension method. The following is one of many sprinkled throughout the web.

Example 2

using static System.Globalization.DateTimeFormatInfo;
internal partial class Program
{
    static void Main(string[] args)
    {

        foreach (var (month, index) in Months.Index())
        {
            Console.WriteLine($"{index,-5}{month}");
        }

        Console.ReadLine();

    }

    public static List<string> Months => CurrentInfo.MonthNames[..^1].ToList();
}

public static class Extensions
{
    public static IEnumerable<(T item, int index)> Index<T>(this IEnumerable<T> sender)
        => sender.Select((item, index) => (item, index));
}
Enter fullscreen mode Exit fullscreen mode

The code sample above produces the exact same output as the last.

Example 3

This example is native to NET9, no extra extension methods needed, use Index extension in the .NET Core Framework.

internal partial class Program
{
    static void Main(string[] args)
    {

        foreach (var (month, index) in Months.Index())
        {
            Console.WriteLine($"{index,-5}{month}");
        }

        Console.ReadLine();

    }

    public static List<string> Months => CurrentInfo.MonthNames[..^1].ToList();
}
Enter fullscreen mode Exit fullscreen mode

The code sample above produces the exact same output as the last two.

Enumerable.CountBy<TSource,TKey>

CountBy returns the count of elements in the source sequence grouped by key.

CountBy requires less code than the conventional GroupBy method.

The following is used for the GroupBy and CountBy examples.

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public Role Role { get; set; }
}

public enum Role
{
    Admin,
    Member,
    Guest
}
public static List<User> Users() =>
[
    new() { Id = 1, UserName = "John", Role = Role.Admin },
    new() { Id = 2, UserName = "Jane", Role = Role.Member },
    new() { Id = 3, UserName = "Joe", Role = Role.Guest },
    new() { Id = 4, UserName = "Alice", Role = Role.Admin },
    new() { Id = 5, UserName = "Bob", Role = Role.Member },
    new() { Id = 6, UserName = "Charlie", Role = Role.Guest },
    new() { Id = 7, UserName = "Dave", Role = Role.Admin },
    new() { Id = 8, UserName = "Eve", Role = Role.Member },
    new() { Id = 9, UserName = "Frank", Role = Role.Guest },
    new() { Id = 10, UserName = "Grace", Role = Role.Admin }
];
Enter fullscreen mode Exit fullscreen mode

The task is to get a count for each Role.

GroupBy example

private void GroupByWithCount()
{
    var users = MockedData.Users();

    var groupedUsers = users.GroupBy(user => user.Role)
        .Select(group => new { Role = group.Key, Count = group.Count() });

    foreach (var group in groupedUsers)
    {
        Debug.WriteLine($"Role: {group.Role}, Count: {group.Count}");
    }
}
Enter fullscreen mode Exit fullscreen mode

CountBy example

private void CountByWithCount()
{
    var users = MockedData.Users();

    foreach (var roleCount in users.CountBy(user => user.Role))
    {
        Debug.WriteLine($"There are {roleCount.Value} users with the role {roleCount.Key}");
    }

}
Enter fullscreen mode Exit fullscreen mode

Both examples, except for minor differences in their output, both are acceptable while CountBy at first glance is easier to tell what its doing. Some will prefer one over the other, as always having options is a good thing.

AggregateBy

AggregateBy applies an accumulator function over a sequence, grouping results by key.

The following is used for the GroupBy and AggregateBy examples.

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public Role Role { get; set; }
}

public enum Role
{
    Admin,
    Member,
    Guest
}

public class MockedData
{

    public static List<User> Users() =>
    [
        new() { Id = 1, UserName = "John", Role = Role.Admin },
        new() { Id = 2, UserName = "Jane", Role = Role.Member },
        new() { Id = 3, UserName = "Joe", Role = Role.Guest },
        new() { Id = 4, UserName = "Alice", Role = Role.Admin },
        new() { Id = 5, UserName = "Bob", Role = Role.Member },
        new() { Id = 6, UserName = "Charlie", Role = Role.Guest },
        new() { Id = 7, UserName = "Dave", Role = Role.Admin },
        new() { Id = 8, UserName = "Eve", Role = Role.Member },
        new() { Id = 9, UserName = "Frank", Role = Role.Guest },
        new() { Id = 10, UserName = "Grace", Role = Role.Admin }
    ];

    public static (string name, string department, int vacationDaysLeft)[] Employees =
    [
        ("John Doe", "IT", 12),
        ("Eve Peterson", "Marketing", 18),
        ("John Smith", "IT", 28),
        ("Grace Johnson", "HR", 17),
        ("Nick Carson", "Marketing", 5),
        ("Grace Morgan", "HR", 9)
    ];

}
Enter fullscreen mode Exit fullscreen mode

GroupBy example

private static void GroupByAggregateBySample()
{
    var kvp = MockedData.Employees
        .GroupBy(emp => emp.department)
        .Select(group => new KeyValuePair<string, int>(group.Key, group.Sum(emp 
            => emp.vacationDaysLeft)));

    foreach (var (key, value) in kvp)
    {
        Debug.WriteLine($"Department {key,-15} has a total of {value} vacation days left.");
    }
}
Enter fullscreen mode Exit fullscreen mode

AggregateBy_ example

private static void AggregateBySample()
{
    var kvp = MockedData.Employees
        .AggregateBy(emp => emp.department, 0, (acc, emp)
            => acc + emp.vacationDaysLeft);

    foreach (var (key, value) in kvp)
    {
        Debug.WriteLine($"Department {key,-15} has a total of {value} vacation days left.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Output is identical for both of the above.

Department IT              has a total of 40 vacation days left.
Department Marketing       has a total of 23 vacation days left.
Department HR              has a total of 26 vacation days left.
Enter fullscreen mode Exit fullscreen mode

UUID v7 generation

Prior to NET 9 the following NuGet package UUIDNext was needed to create UUID/GUID.

With NET 9.

//Creates a new Guid according to RFC 9562, following the Version 7 format.
var item1 = Guid.CreateVersion7();

//Creates a new Guid according to RFC 9562, following the Version 7 format with DateTimeOffset
var item2 = Guid.CreateVersion7(TimeProvider.System.GetUtcNow());

Debug.WriteLine(item1);
Debug.WriteLine(item2);
Enter fullscreen mode Exit fullscreen mode

Summary

Several new features for NET 9 have been presented which were not in 17.12 release nots for Visual Studio 2022 that may be beneficial to developers.

Source code was done in a Windows Forms project which allows all features presented to be done in one project rather than several projects.

Source code

Top comments (0)