a vintage camera

Two new LINQ methods in .NET 9: CountBy and Index

LINQ doesn’t get new features with each release of the .NET framework. It just simply works. This time, .NET 9 introduced two new LINQ methods: CountBy() and Index(). Let’s take a look at them.

1. CountBy

CountBy groups the elements of a collection by a key and counts the occurrences of each key. With CountBy, there’s no need to first group the elements of a collection to count its occurrences.

For example, let’s count all movies in our catalog by release year, of course, using CountBy(),

var movies = new List<Movie>
{
    new Movie("Titanic", 1997, 4.5f),
    new Movie("The Fifth Element", 1997, 4.6f),
    new Movie("Forrest Gump", 1994, 4.3f),
    new Movie("Terminator 2", 1991, 4.7f),
    new Movie("Armageddon", 1998, 3.35f),
    new Movie("Platoon", 1986, 4),
    new Movie("My Neighbor Totoro", 1988, 5),
    new Movie("Pulp Fiction", 1994, 4.3f),
};

var countByReleaseYear = movies.CountBy(m => m.ReleaseYear);
//                              ^^^^^^^
foreach (var (year, count) in countByReleaseYear)
{
    Console.WriteLine($"{year}: [{count}]");
}

// Output
// 1997: [2]
// 1994: [2]
// 1991: [1]
// 1998: [1]
// 1986: [1]
// 1988: [1]

Console.ReadKey();

record Movie(string Name, int ReleaseYear, float Rating);

CountBy() returns a collection of KeyValuePair with the key in the first position and the count in the second one.

By the way, if that Console application doesn’t look like one, it’s because we’re using three recent C# features: the Top-level statements, records, and global using statements.

Before .NET 9.0, we needed to use GroupBy() with a second parameter to transform each group, like this,

var countByReleaseYear = movies.GroupBy(
  x => x.ReleaseYear,
  (releaseYear, movies) => new
  // ^^^^^
  {
      Year = releaseYear,
      Count = movies.Count()
      //      ^^^^^
  });

CountBy() has the same spirit of DistinctBy, MinBy, MaxBy, and other LINQ methods from .NET 6.0. With these methods, we apply an action direcly on a collection using a key selector. We don’t need to filter or group a collection first to apply that action.

Cinematographer's room
Photo by Noom Peerapong on Unsplash

2. Index

Index projects every element of a collection alongside its position in the collection.

Let’s “index” our catalog of movies,

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)
};

foreach (var (index, movie) in movies.Index())
//                                    ^^^^^
{
    Console.WriteLine($"{index}: [{movie.Name}]");
}

// Output
// 0: [Titanic]
// 1: [The Fifth Element]
// 2: [Terminator 2]
// 3: [Avatar]
// 4: [Platoon]
// 5: [My Neighbor Totoro]

Console.ReadKey();

record Movie(string Name, int ReleaseYear, float Rating);

Unlike CountBy(), Index() returns named tuples. It returns IEnumerable<(int Index, TSource Item)>.

Before, we had to use the Select() overload or roll our own extension method. In fact, this is one of the helpful extension methods I use to work with collections. But I call it Enumerated().

If we take a look at the Index source code on GitHub, it’s a foreach loop with a counter in its body. Nothing fancy!

Voilà! Those are two new LINQ methods in .NET 9.0: CountBy() and Index(). It seems the .NET team is bringing to the standard library the methods we needed to roll ourselves before.

To learn about LINQ and other methods, check my quick guide to LINQ, five common LINQ mistakes and how to fix them, and peeking into LINQ DistinctBy source code.

If you want to write more expressive code to work with collections, check my course Getting Started with LINQ on Educative, where I cover from what LINQ is, to refactoring conditionals with LINQ and to the its new methods and overloads in .NET6. All you need to know to start using LINQ in your everyday coding.

Happy coding!