Using MinBy and MaxBy in C# 10 / .NET 6
The .NET team has made some helpful additions to LINQ over the last few years. Today let's check out MinBy and MaxBy from C# 10 / .NET 6.
Microsoft made a couple new additions to LINQ as part of the C# 13 / .NET 9 release a few weeks ago, and, since I happen to really like LINQ, I wrote about how to use them. That got me thinking about other recent additions I might've missed in the last few releases, so I started looking back. And whatdya know, we got a slew of updates to LINQ in C# 10 / .NET 6 a few years ago.
Let's take a look at two of them - MaxBy and MinBy - right after a brief overview of what we had before. Makes it a little easier to appreciate the new stuff!
Finding a Simple 'Max'
Normally, when we want to find the max or min value of some collection of primitive types, then a simple call to Max
or Min
will do. With a list of integers, for example, the max number is the highest. No surprise there.
Implementing IComparer<T>
For other types, we can implement the IComparer<T>
interface, telling Max
and Min
what to use for the comparison. It can be really complex, or as simple as looking at one property:
In this case, the max or min Employee
is just whoever makes the most or least:
var employees = new List<Employee>
{
new("Ed", "Accounting", 78_000, new(2010,3,2), 1),
new("Ned", "Accounting", 120_000, new(2000,2,6), 2),
new("Ted", "Accounting", 94_000, new(2020,1,1), 0),
new("Bob", "Accounting", 55_000, new(2015,7,2), 3)
};
var max = employees.Max(); // Ned
var min = employees.Min(); // Bob
Console.WriteLine($"{max!.Name} makes the most at {max.Salary:C}, " +
$"while {min!.Name} makes the least at {min.Salary:C}.");
// Ned makes the most at $120,000.00, while Bob makes the least at $55,000.00.
Using MinBy and MaxBy
The new MaxBy
and MinBy
methods let us specify a property to sort by, which allows for more flexbility in simple cases. We can sort employees by hire date, for example, and then sort by their security level immediately after:
var newestHire = employees.MaxBy(e => e.HireDate);
var oldestHire = employees.MinBy(e => e.HireDate);
Console.WriteLine($"{newestHire!.Name} was the newest hire on {newestHire.HireDate}, " +
$"while {oldestHire!.Name} was hired long ago on {oldestHire.HireDate}.");
// Ted was the newest hire on 1/1/2020, while Ned was hired long ago on 2/6/2000.
var mostSecurePersonnel = employees.MaxBy(e => e.SecurityLevel);
var leastSecurePersonnel = employees.MinBy(e => e.SecurityLevel);
Console.WriteLine($"{mostSecurePersonnel!.Name} has the highest security level, " +
$"while {leastSecurePersonnel!.Name} has the lowest.");
// Bob has the highest security level, while Ted has the lowest.
Before, if we weren't implementing the interface, then we had to order the collection first and grab the first item, like this:
var maxBySalary = employees.OrderByDescending(e => e.Salary).First();
var minBySalary = employees.OrderBy(e => e.Salary).First();
That isn't much longer, but MaxBy
and MinBy
just read better. The new methods make it easier to tell at a glance what the code is doing, and that's not a trivial thing. We don't have to reason out what the OrderBy
is doing, nor do we have to open the class and investigate the CompareTo
method.
Like anything though, there's limitations. Since we can only specify one property, we still need something like this to consider multiple properties:
var mostCompensatedEmployee = employees
.OrderByDescending(e => e.Salary) // what if two people earn the same salary?
.ThenByDescending(e => e.Overtime) // then we should consider overtime too
.ThenByDescending(e => e.Bonus) // and their bonus, as a final tie-breaker
.First();
We can also use methods that provide a useful value, like the Count()
method on a collection. One more example and then I'm done! Here's two companies, and a quick use of MaxBy
to grab the one with more employees:
var warnerEmps = new Company("Warner Bros", new List<Employee>
{
new("Yakko", "IT", 78_000, new(2010,3,2), 1),
new("Wakko", "IT", 90_000, new(2000,2,6), 2),
new("Dot", "IT", 90_000, new(2000,2,6), 2),
});
var acmeEmps = new Company("Acme Inc", new List<Employee>
{
new("Wile E Coyote", "IT", 78_000, new(2010,3,2), 1),
new("Road Runner", "IT", 90_000, new(2000,2,6), 2),
});
var largestCompany = new[] { warnerEmps, acmeEmps }.MaxBy(c => c.Employees.Count());
Console.WriteLine($"{largestCompany!.Name} is the largest company.");
// Warner Bros is the largest company.
I, for one, welcome any and all additions to LINQ, and can imagine that in many situations MaxBy
and MinBy
will make the code we write a little cleaner. Every bit counts!
Learning More
If you found this content useful, and would like to learn more about a variety of C# features, check out my CSharpDotNetFeatures repo, where you'll find links to plenty more blog posts and practical examples!
Spread the Word