Four new LINQ methods in .NET 6: Chunk, DistinctBy, Take, XOrDefault
27 Jun 2022 #tutorial #csharpLINQ isn’t a new feature in the C# language. It was released back in C# version 3.0 in the early 2000s.
And, after more than ten years, it was finally updated with the .NET 6 release. These are four of the new LINQ methods and overloads in .NET 6.
.NET 6 introduced new LINQ methods like Chunk, DistinctBy, MinBy, and MaxBy. Also, new overloads to existing methods like Take and FirstOrDefault. Before this update, to use similar features, custom LINQ methods or third-party libraries were needed.
1. Chunk
Chunk splits a collection into buckets or “chunks” of at most the same size. It receives the chunk size and returns a collection of arrays.
Let’s say we want to watch all movies we have in our catalog. But, we can only watch three films on a single weekend. Let’s use the Chunk
method for that.
var movies = new List<Movie>
{
new Movie("Titanic", 1998, 4.5f),
new Movie("The Fifth Element", 1997, 4.6f),
new Movie("Terminator 2", 1991, 4.7f),
new Movie("Avatar", 2009, 5),
new Movie("Platoon", 1986, 4),
new Movie("My Neighbor Totoro", 1988, 5)
};
// Split the movies list into chunks of three movies
var chunksOf3 = movies.Where(movie => movie.Rating > 4.5f)
.Chunk(3); // <--
foreach (var chunk in chunksOf3)
{
PrintMovies(chunk);
}
// Output:
// The Fifth Element,Terminator 2,Avatar
// My Neighbor Totoro
static void PrintMovies(IEnumerable<Movie> movies)
{
Console.WriteLine(string.Join(",", movies.Select(movie => movie.Name)));
}
record Movie(string Name, int ReleaseYear, float Rating);
We used three recent C# features: the Top-level statements, records from C# 9.0, and global using statements from C# 10.0. We don’t need all the boilerplate code to write a Console application anymore.
Chunk
returns “chunks” with at most the given size. It returns fewer elements in the last bucket when there aren’t enough elements in the source collection.
2. DistinctBy
Unlike Distinct, DistinctBy receives a delegate to select the property to use as the comparison key and returns the objects containing the distinct values, not only the distinct values themselves.
Let’s find the movies containing unique ratings, not just the ratings.
var movies = new List<Movie>
{
new Movie("Titanic", 1998, 4.5f),
new Movie("The Fifth Element", 1997, 4.6f),
new Movie("Terminator 2", 1991, 4.7f),
new Movie("Avatar", 2009, 5),
new Movie("Platoon", 1986, 4),
new Movie("My Neighbor Totoro", 1988, 5)
};
var distinctRatings = movies.DistinctBy(movie => movie.Rating);
PrintMovies(distinctRatings);
// Output:
// Titanic,The Fifth Element,Terminator 2,Avatar,Platoon
Also, there are similar alternatives to existing methods such as MinBy, MaxBy, ExceptBy, IntersectBy, and UnionBy. They work with a delegate to select a property to use as the comparison key and return the “containing” objects, not only the result.
If we take a look at DistinctBy source code to see what’s inside a LINQ method, it’s not that intimidating after all.
3. Take with Ranges
Take receives a range of indexes to pick a slice of the input collection, not only the first consecutive elements. Take with a range replaces Take followed by Skip to choose a slice of elements.
Let’s choose a slice of our catalog using Take
with ranges and the index-from-end operator.
var movies = new List<Movie>
{
new Movie("Titanic", 1998, 4.5f),
new Movie("The Fifth Element", 1997, 4.6f),
new Movie("Terminator 2", 1991, 4.7f),
new Movie("Avatar", 2009, 5),
new Movie("Platoon", 1986, 4),
new Movie("My Neighbor Totoro", 1988, 5)
};
var rangeOfMovies = movies.Take(^5..3);
PrintMovies(rangeOfMovies);
// Output:
// The Fifth Element,Terminator 2
Take(^5..3)
selects elements starting from the fifth position from the end (^5
) up to the third position from the start (3
). We didn’t need to use the Skip
method for that.
Now that we have Take with ranges is easier to find the last “n” elements of a collection: Take(^n...)
.
Let’s find the last three movies on our catalog.
var movies = new List<Movie>
{
new Movie("Titanic", 1998, 4.5f),
new Movie("The Fifth Element", 1997, 4.6f),
new Movie("Terminator 2", 1991, 4.7f),
new Movie("Avatar", 2009, 5),
new Movie("Platoon", 1986, 4),
new Movie("My Neighbor Totoro", 1988, 5)
};
var lastThreeMovies = movies.Take(^3..);
PrintMovies(lastThreeMovies);
// Output:
// Avatar,Platoon,My Neighbor Totoro
Before this .NET update, we had to use Skip(movies.Count - 3).Take(3)
. Take
did all the work.
4. XOrDefault methods with an optional default value
FirstOrDefault has a new overload to return a default value when the collection is empty or doesn’t have any elements that satisfy the given condition. Other methods with the suffix ‘OrDefault’ have similar overloads.
Let’s find in our catalog of movies a “perfect” film. Otherwise, let’s return our favorite movies from all times.
Before this update, we had to check if FirstOrDefault
returned a non-null value or we had to use the LINQ DefaultIfEmpty method. Like this,
var allTimesFavorite = new Movie("Back to the Future", 1985, 5);
// Using the Null-coalescing assignment ??= operator
var favorite = movies.FirstOrDefault(movie => movie.Rating == 10);
favorite ??= allTimesFavorite;
// ^^^
// Or
// Using the DefaultIfEmpty method
var favorite = movies.Where(movie => movie.Rating == 10)
.DefaultIfEmpty(allTimesFavorite)
// ^^^^^
.First();
With the new overload, we pass a safe default. Like this,
var allTimesFavorite = new Movie("Back to the Future", 1985, 5);
var movies = new List<Movie>
{
new Movie("Titanic", 1998, 4.5f),
new Movie("The Fifth Element", 1997, 4.6f),
new Movie("Terminator 2", 1991, 4.7f),
new Movie("Avatar", 2009, 5),
new Movie("Platoon", 1986, 4),
new Movie("My Neighbor Totoro", 1988, 5)
};
// We have a safe default now. See the second parameter
var favorite = movies.FirstOrDefault(
movie => movie.Rating == 10,
allTimesFavorite); // <--
// We don't need to check for null here
Console.WriteLine(favorite.Name);
// Output:
// Back to the future
Notice the second parameter we passed to the FirstOrDefault
method.
To use these new methods and overloads, install the latest version of the .NET SDK from the .NET official page and use as target framework at least .net6
in your project files.
Here’s a sample csproj file for a Console app using .net6
,
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<!-- ^^^^^ -->
</PropertyGroup>
</Project>
Voilà! These are four new LINQ methods released in the .NET 6 updated. FirstOrDefault
with a safe default helps us prevent one of the common LINQ mistakes: using the XOrDefault
methods without null checking afterwards.
To learn about LINQ and other methods, check my quick guide to LINQ, how to use LINQ GroupBy method and two new LINQ methods in .NET 9.
Happy coding!