Exploring 7 New and Enhanced LINQ APIs in .NET 6

March 20, 2023#Software Development
Article
Author image.

Philippe Vaillancourt, Senior Consultant

LINQ (Language Integrated Query) is a widely used feature in .NET, and it’s great to see that it’s still being built upon and modernized. Most of the changes, especially the enhancements, are LINQ APIs trying to catch up with C# 8, 9, and 10. In this blog post, we’ll cover these new and enhanced APIs, diving into examples and explaining how they can improve your code.

Chunk

One of the new LINQ APIs introduced in .NET 6 is the Chunk method. This method allows you to split an Enumerable into equal-sized chunks. Here’s an example:

var fruits = new[] {"Banana", "Pear", "Apple", "Orange", "Plum", "Lemon"};
var chunks = fruits.Chunk(3);
// chunks == [["Banana", "Pear", "Apple"], ["Orange", "Plum", "Lemon"]]

var largerChunks = fruits.Chunk(4);
// largerChunks == [["Banana", "Pear", "Apple", "Orange"], ["Plum", "Lemon"]]

In the first call, we split the fruits array into two chunks of size 3, which happens to split the array exactly in half. In the second call, we split the array into chunks of size 4, which results in one chunk of size 4 and one chunk of size 2, the remainder.

If you’re wondering when you’d use this, here are a few example use cases:

  • Parallel processing: Splitting a large dataset into smaller chunks can be useful for parallel processing, where each chunk can be processed independently on a separate thread or distributed across a cluster of machines.
  • Pagination: When displaying large sets of data in a user interface, it is often more convenient to display smaller subsets (or pages) of the data. The chunk method can be used to divide the data into these pages.
  • Batch processing: When dealing with APIs or services that have rate limits or can only process a certain number of items at a time, you can use the chunk method to divide the data into smaller batches that meet the requirements of the service.
  • Memory management: When processing large datasets that may not fit entirely in memory, you can use the chunk method to process the data in smaller, manageable pieces, freeing memory as you go.
  • Buffering: In data streaming applications, you may want to accumulate data in memory until a certain chunk size is reached before processing it, to improve performance by reducing the overhead of frequent small processing tasks.
  • Statistical analysis: When performing statistical analysis on large datasets, you can use the chunk method to calculate summary statistics or other aggregates for each chunk, and then combine the results.

TryGetNonEnumeratedCount

The TryGetNonEnumeratedCount method aims to determine the number of elements in a sequence without forcing enumeration, preventing multiple enumerations and potentially improving performance.

IEnumerable<string> fruits = new[] {"Banana", "Pear", "Apple"};

// var count = names.Count(); // Forces enumeration
// instead, use TryGetNonEnumeratedCount
if (fruits.TryGetNonEnumeratedCount(out int count))
{
    // Use count without forcing enumeration
}

TryGetNonEnumeratedCount performs a series of type tests, identifying common subtypes whose count can be determined without enumerating. This includes ICollection<T>, ICollection, and internal types used in the LINQ implementation. If the type is not known to support non-enumerated counting, the method will return false and the count will be set to 0. At which point you can fall back to the Count method, which will force enumeration.

Zip with three sequences

The Zip method allows you to combine multiple sequences into a single sequence of tuples. In .NET 6, you can now zip three sequences together:

var fruits = new[] {"Banana", "Pear", "Apple"};
var colors = new[] {"Yellow", "Green", "Red"};
var origin = new[] {"Spain", "U.S", "Canada"};

var zipped = fruits.Zip(colors, origin);
// zipped == [("Banana", "Yellow", "Spain"), ("Pear", "Green", "U.S"), ("Apple", "Red", "Canada")]

This method has nothing to do with compressing files using the ZIP format. The term comes from the analogy of a physical zipper, like the one found on clothing or bags. When you zip two sides of a zipper together, the individual teeth from each side interlock, creating a single combined structure. Similarly, in programming, when you “zip” two or more collections together, you’re interlocking their elements to form a new combined structure.

Here are some example use cases for the Zip method:

  • Merging parallel data: If you have two or more collections that represent parallel data, such as separate lists for names and ages of people, you can use the Zip method to merge the data into a single collection of pairs or tuples.
  • Vector and matrix operations: The Zip method can be useful for performing element-wise operations on vectors or matrices, such as adding or subtracting the corresponding elements of two vectors.
  • Interleaving data: If you need to interleave the elements of two or more collections, the Zip method can be used to create a combined collection where elements from each input collection appear in an alternating pattern.
  • Comparing collections: The Zip method can be used to compare elements from two or more collections, such as finding the element-wise minimum or maximum between them or checking if they have the same elements in the same order.
  • Combining data from multiple sources: When processing data from multiple sources, such as files or database tables, the Zip method can be used to combine the data from different sources into a single, unified structure for further processing or analysis.
  • Creating dictionaries or maps: The Zip method can be used to create dictionaries or maps by combining two collections, one containing keys and the other containing values, into a single collection of key-value pairs.

MinBy and MaxBy

The MinBy and MaxBy methods allow you to find the minimum and maximum elements of a sequence based on a given selector. This can make your code more concise and easier to read:

IEnumerable<(string Name, int Speed)> cars = new[] {("Corvette", 150), ("Chevette", 50), ("Corolla", 100)};

// Before
var fastestCar = cars.OrderByDescending(car => car.Speed).First();
var slowestCar = cars.OrderBy(car => car.Speed).First();

// Now
var fastestCar = cars.MaxBy(car => car.Speed);
// fastestCar == ("Corvette", 150)
var slowestCar = cars.MinBy(car => car.Speed);
// slowestCar == ("Chevette", 50)

Index support for ElementAt

The ElementAt method now supports the C# 8 index operator:

IEnumerable<string> fruits = new[] {"Banana", "Pear", "Apple", "Orange", "Plum", "Lemon"};

// Before
var fourthFromEnd = fruits.ElementAt(fruits.Count() - 4);

// Now
var fourthFromEnd = fruits.ElementAt(^4);
// fourthFromEnd == "Apple"

Range support for Take

The Take method now supports the C# 8 range operator:

IEnumerable<string> fruits = new[] {"Banana", "Pear", "Apple", "Orange", "Plum", "Lemon"};

// Before
var slice = fruits.Skip(2).Take(2);

// Now
var slice = fruits.Take(2..4);
// slice == ["Apple", "Orange"]

Mix and match index and range

You can also mix and match index and range operators in LINQ:

var fruits = new[] {"Banana", "Pear", "Apple", "Orange", "Plum", "Lemon"};

var lastThree = names.Take(^3..);
// lastThree == ["Orange", "Plum", "Lemon"]

Conclusion

These new and enhanced LINQ APIs in .NET 6 provide developers with more powerful and concise ways to write code. I hope you enjoyed this blog post and that you’ll try out these new APIs in your own code.


Copyright © 2024 NimblePros - All Rights Reserved