Exploring LINQ in .NET 6: Mastering DistinctBy and More

I'm an "old-school" Software Engineer, mostly specializing in C# and .NET.
Currently in the process of discovering AI and Machine Learning.
Search for a command to run...

I'm an "old-school" Software Engineer, mostly specializing in C# and .NET.
Currently in the process of discovering AI and Machine Learning.
No comments yet. Be the first to comment.
One key reason I find myself drawn to social media is its power to inspire through the diverse perspectives of people. People tweeting or blogging about stuff they do or like, youtube videos and podcasts on different topics, all of these embed little...

Whether you're a seasoned Microsoft tech stack developer or just starting out, this tutorial is the ideal place to begin your Python journey. And, to sweeten things up a little bit, you're going to use ChatGPT to help you get started! Without further...

In line with the title of this blog, I'd like to start posting articles about languages, tools and technologies that I got a chance to use recently. One of the technologies that I was lucky to learn is PostgreSQL. I have always been an avid Microsoft...

Shakespeare will hopefully forgive me for abusing a line from his famous Hamlet. In my post, I won't be covering questions of life and death, but rather something much more prosaic - a lesser-known LINQ materialization method - ToLookup. We all know ...

Since the inception of LINQ, methods suffixed with 'By' have played an important role in simplifying LINQ operations by introducing an additional 'selector' parameter.
A typical example is the OrderBy method. Without the selector parameter, even a straightforward task like sorting a list of objects by one of their properties would become needlessly complex, requiring a custom comparer implementation to select the appropriate property or properties for sorting.
// Using OrderBy to sort a list of Points based on the "X" member
var orderedByX = (new List<Point> { new Point(2, -2), new Point(-2, 2), new Point(0, 0) })
.OrderBy(pt => pt.X);
Now, as long as we've had these invaluable LINQ methods, there's been a desire for similar selector-based methods to be available for other LINQ functions. A notable case is the LINQ Distinct method, where this extended functionality has been sorely missed. (I'm aware of the third-party libraries that have provided these capabilities, but having these features as part of the core framework is always preferable.)
This longing has finally been addressed with the arrival of .NET6, which introduces out-of-the-box support for six new selector-based methods. These additions significantly simplify our LINQ query building process, enhancing both efficiency and clarity.
The new methods are:
DistinctBy
ExceptBy
IntersectBy
MinBy/MaxBy
UnionBy
DistinctBy returns unique (distinct) items from a list, determined by a key specified through a selector function. This method is particularly beneficial when dealing with large datasets, as DistinctBy can optimize performance by reducing the dataset to only unique items based on a particular property.
To illustrate, let’s consider an example where DistinctBy is used to quickly extract a subset of unique items from a collection based on a specific property. Specifically, we extract unique Person objects from a list based on their Name, effectively removing duplicates while ignoring other properties like age.
var people = new List<Person>
{
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Alice", 35)
};
var distinctNames = people.DistinctBy(p => p.Name);
In the world of relational databases, functions like MIN and MAX are fundamental tools for retrieving the smallest and largest values from a dataset. When transitioning to C# and working with collections, the MinBy and MaxBy methods introduced in .NET6 offer an intuitive parallel.
Suppose you have a list of products, each with a name and a price. You want to find the product with the lowest price. MinBy makes this straightforward:
var products = new List<Product>
{
new Product("Laptop", 1200),
new Product("Smartphone", 800),
new Product("Tablet", 600)
};
var cheapestProduct = products.MinBy(p => p.Price);
Here is an example identifying the most recent log entry from a list of entries by selecting the one with the latest timestamp:
var logEntries = new List<LogEntry>
{
new LogEntry(DateTime.Parse("2023-03-01"), "System startup"),
new LogEntry(DateTime.Parse("2023-03-05"), "User logged in"),
new LogEntry(DateTime.Parse("2023-03-03"), "System update completed")
};
var latestEntry = logEntries.MaxBy(entry => entry.Timestamp);
The last three methods we'll cover in this post are UnionBy, ExceptBy and IntersectBy. These methods provide enhanced control over how sets are combined or compared based on specific properties or criteria.
UnionBy is used to combine two collections into one while eliminating duplicates based on a specified key selector.
ExceptBy is used to create a collection that contains elements from the first collection that are not present in the second, again based on a specified key selector.
IntersectBy finds common elements between two collections based on a specified key selector.
Here is an example scenario demonstrating all three methods to manage product inventories across multiple stores: using UnionBy to create a combined inventory list, ExceptBy to remove items that are out of stock, and IntersectBy to identify items that are common to all stores for a promotional campaign.
var store1Products = new List<Product>
{
new Product(1, "Laptop"),
new Product(2, "Smartphone"),
new Product(3, "Tablet")
};
var store2Products = new List<Product>
{
new Product(4, "Camera"),
new Product(2, "Smartphone"),
new Product(3, "Tablet")
};
var outOfStockProducts = new List<Product>
{
new Product(2, "Smartphone")
};
// UnionBy: Combine product lists from both stores, avoiding duplicate IDs
var combinedInventory = store1Products
.UnionBy(store2Products, p => p.ID);
// ExceptBy: Remove out-of-stock products from the combined inventory
var availableInventory = combinedInventory
.ExceptBy(outOfStockProducts.Select(p => p.ID), p => p.ID);
// IntersectBy: Find common products available in both stores for a promotion
var commonProductsForPromotion = store1Products
.IntersectBy(store2Products.Select(p => p.ID), p => p.ID);
It's worth noting that these new methods are primarily designed for in-memory LINQ queries (LINQ to Objects). As of the most recent update, Entity Framework Core (currently at EF Core 8) does not yet support these new LINQ methods.
Have you had a chance to work with these selector-based LINQ methods yet? I'm curious to hear how you're integrating the new .NET 6 methods into your coding projects. Are there any other methods you're hoping to see in future updates? Feel free to share your experiences and insights in the comments below.
For more deep dives into LINQ, don't forget to explore other posts in my LINQ Gems series.