.NET9 has introduced two new methods, CountBy
and AggregateBy
, making data aggregation easier and more efficient without the need for complex intermediate steps like GroupBy
.
CountBy
CountBy
enables quick calculation of key frequencies. In the following example, we’ll find the most frequently occurring word in a given text string:
string sourceText = @"In the future, technology will revolutionize every aspect of our lives.
The future holds incredible potential for innovation and progress.
As we look to the future, we must prepare ourselves for new challenges ahead.
The future workforce will require new skills and adaptability.
Embracing our future means embracing change and embracing opportunity.";
// Find the most frequent word in the text.
var wordFrequencies = sourceText
.Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(word => word.ToLowerInvariant())
.CountBy(word => word);
// Get the most frequent word and its frequency.
var mostFrequentWord = wordFrequencies.MaxBy(pair => pair.Value);
Console.WriteLine($"The most frequent word is '{mostFrequentWord.Key}' with a frequency of {mostFrequentWord.Value}.");
gives us this output for the given text:
The most frequent word is 'future' with a frequency of 5.
AggregateBy
AggregateBy
allows us to implement versatile workflows. Here’s an example demonstrating score calculation associated with a given key:
// Assuming we have data of students' scores in different subjects in different classes
(string subject, int subjectScore, int classId)[] studentScores =
{
("Software Engineering", 83, 1),
("Computer Networks", 72, 1),
("Artificial Intelligence", 93, 1),
("Software Engineering", 81, 2),
("Computer Networks", 78, 2),
};
// Aggregate scores by subject ID.
var aggregatedScores =
studentScores.AggregateBy(
keySelector: entry => entry.subject,
seed : 0,
(totalScore, curr) => totalScore + curr.subjectScore
);
foreach (var item in aggregatedScores)
{
Console.WriteLine($"Subject : {item.Key}, Total Score: {item.Value}");
}
produces this output:
Subject : Software Engineering, Total Score: 164
Subject : Computer Networks, Total Score: 150
Subject : Artificial Intelligence, Total Score: 93
Index
With Index
, we can now get the index collection for an IEnumerable. When calling Index
, it returns a tuple of (int, TSource)
.
With Index<TSource>(IEnumerable<TSource>)
you can now automatically index items in a collection, as shown below:
IEnumerable<string> lines = File.ReadAllLines("carDetails.txt");
foreach ((int index, string line) in lines.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
would produce an output like this:
Line number: 1, Line: Toyota Camry, Sedan, 2019, Silver
Line number: 2, Line: Ford Mustang, Convertible, 2018, Red
Line number: 3, Line: Chevrolet Tahoe, SUV, 2021, Black
Line number: 4, Line: Tesla Model S, Electric, 2022, White
Line number: 5, Line: BMW 3 Series, Sedan, 2020, Gray
Index Example 2
Given this list of games and their prices
var games = new List<Game>
{
new Game { Name = "Werewolf", Genre = "Party", Price = 9.99m },
new Game { Name = "Splendor", Genre = "Strategy", Price = 34.99m },
new Game { Name = "Azul", Genre = "Abstract", Price = 29.99m },
new Game { Name = "Forbidden Planet", Genre = "Co-op", Price = 24.99m },
new Game { Name = "Catan", Genre = "Strategy", Price = 44.99m },
new Game { Name = "Ticket to Ride", Genre = "Strategy", Price = 49.99m },
new Game { Name = "Hive", Genre = "Abstract", Price = 24.99m },
new Game { Name = "Spyfall", Genre = "Party", Price = 14.99m },
new Game { Name = "Spirit Island", Genre = "Co-op", Price = 59.99m },
new Game { Name = "Draftosaurus", Genre = "Family", Price = 19.99m },
new Game { Name = "7 Wonders", Genre = "Strategy", Price = 39.99m }
};
we want to create a list of our games from most expensive to least expensive and then alphabetically within the price. Let’s display it as a numbered list. Keep in mind that we’re looked at weirdly for starting our lists at 0, so we’ll start our list numbering at 1.
We will use LINQ’s OrderByDescending
and ThenBy
to order our List<Game>
to achieve our ordering goal. Then, we will chain an Index
call to get the index for that ordered collection’s results:
IEnumerable<(int Index, Game Item)> indexedMostExpensiveGames = games
.OrderByDescending(game => game.Price)
.ThenBy(game => game.Name)
.Index();
// Output the results
Console.WriteLine("Games - Most to Least Expensive:");
// Explicitly typing the tuple returned in the Index()
foreach ((int index, Game game) orderedGameEntry in indexedMostExpensiveGames)
{
Console.WriteLine($"{orderedGameEntry.index + 1}. {orderedGameEntry.game.Name} - ${orderedGameEntry.game.Price:F2}");
}
will give us this output:
Games - Most to Least Expensive:
1. Spirit Island - $59.99
2. Ticket to Ride - $49.99
3. Catan - $44.99
4. 7 Wonders - $39.99
5. Splendor - $34.99
6. Azul - $29.99
7. Forbidden Planet - $24.99
8. Hive - $24.99
9. Draftosaurus - $19.99
10. Spyfall - $14.99
11. Werewolf - $9.99
For the sake of clarity, the older LINQ aggregate methods should also be mentioned here for grouping/aggregating data based on specific properties or conditions.
GroupBy
The GroupBy
method allows you to group elements in a collection based on a key selector function. The result is a collection of groups, each containing elements with the same key value.
Example:
List<Product> products = GetProducts();
var groupedProducts = products.GroupBy(p => p.Category);
foreach (var group in groupedProducts)
{
Console.WriteLine($"Category: {group.Key}");
foreach (var product in group)
{
Console.WriteLine($" Product: {product.Name}");
}
}
GroupJoin
The GroupJoin
method allows you to perform a grouped join between two collections based on a key selector function for each collection.
Example:
List<Customer> customers = GetCustomers();
List<Order> orders = GetOrders();
var customerOrders = customers.GroupJoin(orders,
customer => customer.Id,
order => order.CustomerId,
(customer, orderGroup) => new
{
Customer = customer,
Orders = orderGroup
});
foreach (var item in customerOrders)
{
Console.WriteLine($"Customer: {item.Customer.Name}");
foreach (var order in item.Orders)
{
Console.WriteLine($" Order: {order.Id}");
}
}