.NET 6 LINQ New Features
What is .NET 6
and why should I care?
.NET 6
is the upcoming major overhaul for .NET
. It unifies entire .NET
experience. No more .NET Core
, .NET Full Framework
, Xamarin
, Mono
, etc. Just a single .NET
.
New LINQ
Features
At the time of writing this post, 5 previews of .NET 6
have been released and last couple of releases have been blessing for LINQ
.
Here are few top new LINQ
features:
New methods
MaxBy
andMinBy
New methods
Chunk
New methods
DistinctBy
,UnionBy
,IntersectBy
, andExceptBy
Index
andRange
parametersNew method
TryGetNonEnumeratedCount
Default parameters for
FirstOrDefault
,LastOrDefault
, andSingleOrDefault
Zip
supports 3IEnumerables
New methods MaxBy
and MinBy
Finding out maximum or minimum has been easier than even with these new MaxBy
or MinBy
methods.
Suppose a List
of Person
s as:
static List<Person> people = new List<Person>
{
new Person { Id = 1, Name = "Amit", Age = 38},
new Person { Id = 2, Name = "Ravi", Age = 36},
new Person { Id = 3, Name = "Manish", Age = 34},
new Person { Id = 4, Name = "Satish", Age = 29},
};
If we need to get maximum and minimum, currently here's how to do it:
//Without using MaxBy and MinBy
Person oldestPerson = people.OrderByDescending(person => person.Age).First();
Person youngestPerson = people.OrderBy(person => person.Age).First();
Console.WriteLine($"Oldest Person without using MaxBy: {oldestPerson.Name}");
Console.WriteLine($"Youngest Person without using MaxBy: {youngestPerson.Name}");
But with MaxBy
and MinBy
, it's just a single method call:
Person oldestPerson = people.MaxBy(person => person.Age);
Person youngestPerson = people.MinBy(person => person.Age);
Console.WriteLine($"Oldest Person using MaxBy: {oldestPerson.Name}");
Console.WriteLine($"Youngest Person using MaxBy: {youngestPerson.Name}");
Neat, right? Let me know in the comments if you have alternate methods of getting maximum and minimum using LINQ
.
New method Chunk
A new method Chunk
slices the IEnumerable
into provided sizes. e.g. If you have a collection of 4 elements and you need to cluster them into fixed size of 2, here's how to do it:
IEnumerable<Person[]> cluster = people.Chunk(2);
// Print each cluster.
foreach(var people in cluster)
{
Console.WriteLine($"Cluster of {string.Join(",", people.Select(person => person.Name))}");
}
//Prints
// Cluster of Amit,Ravi
// Cluster of Manish,Satish
New methods DistinctBy
, UnionBy
, IntersectBy
, and ExceptBy
Existing set methods Distinct
, Union
, Intersect
, and Except
have been powered up by these new methods which can take a selector function to operate. e.g.
IEnumerable<Person> evenAgedPeople = people.Where(person => person.Age % 2 == 0);
//Amit,Ravi,Manish
IEnumerable<Person> personAbove35 = people.Where(person => person.Age > 35);
//Amit,Ravi
IEnumerable<Person> union = evenAgedPeople.UnionBy(personAbove35, x => x.Age);
//Amit,Ravi,Manish
IEnumerable<Person> intersection = evenAgedPeople.IntersectBy(personAbove35.Select(p => p.Age), x => x.Age);
//Amit,Ravi
Let me know in the comments, the usage of DistinctBy
and ExceptBy
.
Index
and Range
parameters
Range
: ..
, and Index
: ^
already exist in C#
8, .NET 6
brings these two to LINQ
.
- The
ElementAt
operator now takes indices from the end.
Person secondLastPerson = people.ElementAt(^2);
//Manish
Skip
andTake
now takeRange
as well:
IEnumerable<Person> take3People = people.Take(..3);
//Amit,Ravi,Manish
IEnumerable<Person> skip1Person = people.Take(1..);
//Ravi,Manish,Satish
IEnumerable<Person> take3Skip1People = people.Take(1..3);
//Ravi,Manish
IEnumerable<Person> takeLast2People = people.Take(^2..);
//Manish,Satish
IEnumerable<Person> skipLast3People = people.Take(..^3);
//Amit
IEnumerable<Person> takeLast3SkipLast2 = people.Take(^3..^2);
//Ravi
New method TryGetNonEnumeratedCount
Sometimes you need to get a count without the enumeration. TryGetNonEnumeratedCount
will try to get a count without forcing an enumeration. It internally checks for the implementation of ICollection
i.e. IEnumerable
- that already has a mechanism to get a count without forcing an enumeration. Otherwise, it'll try to take advantage of new improvements in LINQ
. Useful in scenarios where you want to get a count but not at the cost of enumerating the IEnumerable
. e.g.
List<Person> anotherList = people.TryGetNonEnumeratedCount(out int count) ? new List<Person>(count): new List<Person>();
anotherList.AddRange(people);
In our case right now, we know that people is just an in-memory list, but what if it was from a database of some Stream
, then it would've made sense to find out the count to optimize the instantiation of anotherList
object by specifying capacity
parameter.
Default parameters for FirstOrDefault
, LastOrDefault
, and SingleOrDefault
Current FirstOrDefault
, LastOrDefault
, and SingleOrDefault
methods return default(T)
if the source IEnumerable
is empty. The new overloads accept a parameter that will be returned if the source is empty.
e.g.
List<int> emptyList = new List<int>();
int value = emptyList.FirstOrDefault(-1);
//-1 instead of 0
This reminds me of ISNULL
function of SQL Server
.
Zip
supports 3 IEnumerables
Before .NET 6
, Zip
used to take only 2 parameters. Now it takes 3 parameters:
IEnumerable<int> ids = Enumerable.Range(1, 4);
IEnumerable<Person> allPeople = people;
IEnumerable<int> allAges = people.Select(person => person.Age);
IEnumerable<(int Id, Person Person, int Age)> zipped = ids.Zip(allPeople, allAges);
Conclusion
.NET 6
previews bring an awesome set of new features with every new release. Let me know in the comments which features you're excited about.
Next Steps
Download
.NET 6
today and try it out.The source code is available at: https://github.com/iSatishYadav/.net-6-linq-new-features
Originally published at my blog at: https://blog.satishyadav.com/.net-6-linq-new-features
Subscribe to my newsletter
Read articles from Satish Yadav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Satish Yadav
Satish Yadav
Manager — SDE