HomeNikola Knezevic

In this article

Banner

New LINQ methods in .NET 9

18 Nov 2024
5 min

Sponsor Newsletter

Hello! With the release of .NET 9, I’ve decided to celebrate the occasion by launching a mini-series of posts over the coming days, instead of my usual Thursday post.

I hope you’ll enjoy this series as we dive into the exciting new features that .NET 9 has to offer.

To kick things off, the first post will be dedicated to the new LINQ methods introduced in this release. They are created to make common collection operations more concise, readable and efficient.

.NET 9 introduced 3 new LINQ methods to simplify code compared to traditional approaches:

  • Index
  • CountBy
  • AggregateBy

Index

It’s a common need to work with a collection in a way that allows you to iterate through its elements while keeping track of each element’s index.

Traditionally, you’d achieve this with a for loop:

csharp
for (var i = 0; i < orders.Count; i++)
{
    var order = orders[i];
    Console.WriteLine($"Order {i}: {order.Id} {order.Status} {order.CreatedAt}");
}

Another interesting approach you could take is using the Select method with an index. While Select is typically used to transform each element, it also offers an overload that includes the index of each element.

csharp
foreach (var (index, order) in orders.Select((o, i) => (i, o)))
{
    Console.WriteLine($"Order {index}: {order.Id} {order.Status} {order.CreatedAt}");
}

While functional, both approaches introduce unnecessary boilerplate. With the new Index method, you can achieve this in a much cleaner way:

csharp
foreach (var (index, order) in orders.Index())
{
    Console.WriteLine($"Order {index}: {order.Id} {order.Status} {order.CreatedAt}");
}

The Index method eliminates manual index handling and makes your code more expressive and concise.

CountBy

This method becomes particularly useful in scenarios where you need to count the occurrences of items within a collection, such as when analyzing data to determine the most frequently sold products in a list of orders.

Conventional approach to achieve this would involve using the GroupBy method in combination with Select and Count.

csharp
var mostSellingProducts = products
        .GroupBy(p => p.CategoryId)
        .Select(g => new KeyValuePair<Guid, int>(g.Key, g.Count()));

The GroupBy method creates groupings based on each Id, while the Select method generates KeyValuePair objects containing the Id and the count of items within each group.

The CountBy method streamlines this process by combining the grouping and counting steps into one:

csharp
var mostSellingProducts = products
        .CountBy(p => p.CategoryId);

You only need to specify the property by which you want to count occurrences, making easy as it can be.

AggregateBy

The third and final new LINQ method introduced in .NET 9 offers the greatest simplification of logic in my opinion.

Calculating grouped totals is a common requirement, such as determining the total inventory value filtered by a specific criterion (e.g., by product category). This often involves combining both grouping and aggregation operations to achieve the desired result.

Prior to the introduction of AggregateBy, accomplishing this required using the GroupBy method to group the data, followed by a Select to get aggregated values for each group.

csharp
var totalCategoryValues = products
    .GroupBy(x => x.CategoryId)
    .Select(g => new KeyValuePair<Guid, decimal>(
        g.Key,
        g.Sum(x => x.Quantity * x.Price)));

The AggregateBy method combines grouping and aggregation in a single step.

csharp
var totalCategoryValues = products
    .AggregateBy(
        x => x.CategoryId,
        seed: decimal.Zero,
        (totalValue, x) => totalValue + x.Quantity * x.Price);

Using AggregateBy, you can define the aggregation logic directly, leading to cleaner code.

Benchmarks

The benchmark results are also quite interesting.

For the Index method, the difference is marginal with slightly less memory being allocated. However, both AggregateBy and CountBy are significantly faster and do not allocate any memory.

Here are the benchmark results:

Description of Image

It seems the key factor is that these methods utilize CollectionsMarshal.GetValueRefOrAddDefault. This method provides a reference to the value, which helps avoid the overhead of allocation.

Conclusion

In conclusion, while these methods may not be revolutionary, they are created to address common scenarios with more straightforward and expressive solutions.

They can significantly simplify code when you're dealing with complex chains of LINQ methods to achieve results.

Whether you’re iterating with indices, performing grouped aggregations or counting occurrences, these methods will enhance readability.

If you want to conduct additional testing, maybe with different types or simply take a look, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.