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();
}
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));
}
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();
}
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 }
];
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}");
}
}
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}");
}
}
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)
];
}
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.");
}
}
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.");
}
}
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.
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);
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 link
lol