My Favorite C# Features Part 2: LINQ

Jeffrey T. Fritz - Mar 10 '21 - - Dev Community

Working with collections in any programming language always ends up with some sort of loop structure and ugly code to find an object, work on several objects, and exit the loop. This is why I really enjoy using LINQ with C# -- all of this looping and aggregation code is hidden from me by the compiler and I can focus on writing the code that I actually need to get and work with data in a collection. In this post, I'll review some of my favorite LINQ features that make me more productive in working with collections of data.

Throughout the samples below, we'll work with this collection of Hat objects. It's a real collection, and yes I do have these hats IRL. You can see a large selection of my hat collection at https://hats.csharpfritz.com

public class Hat {

  // A name I've assigned to the hat
  public string Name { get; set; }

  // The year I acquired the hat
  public int Year { get; set; }

  // Theme of the hat: Sports, Movies, Tech, etc
  public string Theme { get; set; }
}

public static class HatCollection {
  public static readonly Hat[] Items = new Hat[] {
    new Hat { Name="Camp Crystal Lake", Year=2020, Theme="Movies" },
    new Hat { Name="Marauder's Map", Year=2020, Theme="Movies" },
    new Hat { Name="Super C#", Year=2018, Theme="Tech" },
    new Hat { Name="Dunder Mifflin", Year=2018, Theme="TV" },
    new Hat { Name="Space X", Year=2019, Theme="Tech" },
    new Hat { Name="Phillies 2008 World Series", Year=2008, Theme="Sports" },
    new Hat { Name="The Flash", Year=2021, Theme="TV" }
  };
}
Enter fullscreen mode Exit fullscreen mode

Fritz's The Flash hat

Fritz's The Flash hat

Where - Find Some Data and Return It

What good is a collection of data if you can't search through it quickly with just a 1 line command? The Where method can be used to quickly find the subset of items that meet your condition.

Given the collection of hats, we could return just those hats that were acquired in 2020 with this command:

var twentyTwentyHats = HatCollection.Items.Where(h => h.Year==2020);

// Returns "Camp Crystal Lake" and "Marauder's Map" hat objects
Enter fullscreen mode Exit fullscreen mode

The contents of the Where method are called a lambda expression. You define a lambda with a variable that will capture one of the items in the collection, add a 'fat arrow' and then the operation you want to process on that item. In the case of a Where method, we are testing for a condition and returning a true or false boolean value to indicate if the item should be returned.

In this case, we are testing if the Year property of the Hat is 2020.

Are ANY of these Hats from before 2010?

The Any method is a simple check to return a true or false boolean value indicating if a condition is met. If we want to search the collection for hats that are from before 2010, we could use this expression:

var olderThanTwentyTen = HatCollection.Items.Any(h => h.Year < 2010);

// Returns true due to the presence of the 2008 Phillies hat
Enter fullscreen mode Exit fullscreen mode

The Any method can operate quickly as it will stop looping through the collection as soon as the condition is met.

Count those hats

Aggregate methods are available that allow you to count, sum, min, max, and average values inside the collection. In addition to simply counting all of the items in the collection, we can provide a test method to count only those items that pass that test.

var totalHats = HatCollection.Items.Count();

// This returns 7.. but honestly, 
// Fritz's hat collection is more than 100

var techHatsCount = HatCollection.Items.Count(h => h.Theme == "Tech");

// This will return 2 for "Super C#" and "Space X"
Enter fullscreen mode Exit fullscreen mode

Sorting done simply

Sorting is one of those computer science gotcha questions that ALWAYS comes up in an interview and nobody likes it. When it comes to sorting collections in memory, I just let the experts who built .NET do it for me by using the OrderBy and OrderByDescending methods. Simply provide a selector method that returns the property or value to order by and LINQ will return to you a collection of your objects in the order desired:


var newestHatsFirst = HatCollection.Items.OrderByDescending(h => h.Year);

var orderByTheme = HatCollection.Items.OrderBy(h => h.Theme);
Enter fullscreen mode Exit fullscreen mode

Did you know, you can chain these sort method together, or even connect them with a Where method to create a more interesting query?

var techHatsNewestFirst = HatCollection.Items
    .Where(h => h.Theme == "Tech")
    .OrderByDescending(h => h.Year);

// This returns the "Space X", "Super C#" hat objects
Enter fullscreen mode Exit fullscreen mode

Project your output with Select

Just like in SQL, there is a Select method that can be used with LINQ to transform our collection from one object to another. Perhaps we just want an array of the hat names... easy:

var hatNames = HatCollection.Items.Select(h => h.Name);
Enter fullscreen mode Exit fullscreen mode

You can use select to convert to another type, an anonymous type, or tuples. Use Select to give you the shape and format of the data you need.

Group By FTW!

It's nice to be able to sort and filter your data, but the real power comes in when you summarize a collection. Let's use the GroupBy feature to summarize the number of hats in the collection by theme:

var totalByTheme = HatCollection.Items
    .GroupBy(h => h.Theme)
    .Select(g => new {Theme=g.Key, Count=g.Count() });

foreach (var item in totalByTheme)
{
  Console.WriteLine($"Theme: {item.Theme}, Count: {item.Count}");
}

/* 
Outputs:

Theme: Movies, Count: 2
Theme: Tech, Count: 2
Theme: TV, Count: 2
Theme: Sports, Count: 1

*/
Enter fullscreen mode Exit fullscreen mode

This GroupBy delivers a collection of grouped objects with a Key property that contains the value grouped by, in this case the Theme property. We can then chain the Select method to project these groupings into a summary collection with the themes and the count of hats that belong to each theme.

Summary

This is just the beginnings of the cool things you can do with LINQ. Put together with an object-relational mapper tool like Entity Framework and you have a very cool way to query your database.

Did you know, I host a weekly live stream on the Visual Studio Twitch channel teaching the basics of C#? Tune in on Mondays at 9a ET / 1400 UTC for two hours of learning in a beginner-friendly Q+A format with demos and sample code you can download. I recently had an episode about LINQ that you can find on YouTube:

Looking to get started learning C#? Checkout our free on-demand courses on Microsoft Learn!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .