.Net 6: Linq enhancements

Linq has got some noticeably enhancements in the .Net 6. In this post, we will briefly visit some of them.

Specify Default Value for FirstOrDefault, LastOrDefault, SingleOrDefault

One of the features I am so relieved to see if the support for specifying default value for FirstOrDefault()LastOrDefault(), and SingleOrDefault(). I never quite understood why it wasn’t included in the first place. Previously the mentioned methods would return default(T) if the source return empty. With .Net 6, you could specify the default value which needs to be returned instead of default(t). For example

var data = GetData(10);

// Prior to .Net 6
var firstOrDefault = data.Where(x => x.Id > 100).DefaultIfEmpty(new User(-1, "GhostUser")).First();
var lastOrDefault = data.Where(x => x.Id > 100).DefaultIfEmpty(new User(-1, "GhostUser")).Last();
var singleOrDefault = data.Where(x => x.Id > 100).DefaultIfEmpty(new User(-1, "GhostUser")).Single();


// With .Net
var firstOrDefault = data.FirstOrDefault(x => x.Id > 100, new User(-1, "GhostUser"));
var lastOrDefault = data.LastOrDefault(x => x.Id > 100, new User(-1, "GhostUser"));
var singleOrDefault = data.SingleOrDefault(x => x.Id > 100, new User(-1, "GhostUser"));

The improvement is quite easy to see. The code has become more cleaner and verbose.

KeySelector for ExceptBy/ IntersectBy/ DistinctBy/ UnionBy/ MaxBy/ MinBy

Previously, if you needed to get the Max using a property using Linq methods, it was painful to say the least

var max = data.OrderByDescending(x => x.Id).First();

Things get even more ugly when we attempt Except(),Intersect(),Distinct() and Union() by a property. MoreLinq library had few methods which helped us in this regard, but with the inbuild Linq methods, this was still unavailable, until .Net 6.

The methods Except,Intersect, Distinct, Union, Max, and Min have got a variant which would now receive a key selector (Func<T,TKey>). Here is a short sample

demoCollection.ExceptBy(secondCollection.Select(x=>x.Name),user=>user.Name);
demoCollection.IntersectBy(secondCollection.Select(x => x.Name), user => user.Name);
demoCollection.DistinctBy(user=>user.Name);
demoCollection.UnionBy(secondCollection,user=>user.Name).;
demoCollection.MaxBy(user=>user.Id);
demoCollection.MinBy(user=>user.Id);

Range for Take

Range was introduced in C# 8. .Net 6 now adds an override to the Take() method which would accept a Range. For example,

demoCollection.Take(10..^10);

Chunk

Third Party Linq add-on libraries had this functionality for a long time now, but Microsoft decided to add chunk to its own set of extension methods of IEnumerable with .Net 6. Chunk allows you to process large collection of data in batches. For example, the following method call would split the demoCollection into chunks of 5, so that you could process them as batches.

demoCollection.Chunk(5);

Zip with IEnumerables

Linq methods previously allowed developers to merge an element in the first collection with an element in the second collection using the Zip method.Previously, if you need to Zip 3 collection, you would need to opt for a solution that might look like following.

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
string[] romans = { "I", "II", "III" };

var numbersAndWords = numbers.Zip(words, (number, word) => (number, word));
var numbersAndWordsAndRoman = numbersAndWords.Zip(romans,(numberWord,roman)=> (numberWord.number, numberWord.word,roman));

foreach (var (number, word, roman) in numbersAndWordsAndRoman)
    WriteLine($"{number}-{word}-{roman}");

The new improvements now allows you to include 3 collections in your operation.

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
string[] romans = { "I", "II", "III" };

var numbersAndWords = numbers.Zip(words,romans);

foreach (var (number,word,roman) in numbersAndWords)
    WriteLine($"{number}-{word}-{roman}");

That definitely has got a lot more cleaner.

TryGetNonEnumeratedCount

There are times when you would like to declare explicitly the capacity of the collection. This is quite useful when you do not want the capacity to double each time count exceeds capacity (not to mention all the copy operations involved). The TryGetNonEnumeratedCount method attempts to obtain the count of the source enumerable without forcing an enumeration. If it doesn’t find a property (say Count from ICollection) then it would return 0;

demoCollection.TryGetNonEnumeratedCount(out var enumCount); 

Those are some of the improvements in .Net 6 for Linq. We will continue exploring the .Net 6 features in the blogs to come. For the complete source of the code in the post, please use my Github.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s