C# LINQ Hacks Every Developer Should Know

C# LINQ Hacks Every Developer Should Know

Alex Cassells .NET, Articles, C# Leave a Comment

LINQ (which stands for Language Integrated Query) is a library of collection manipulation methods that makes organizing and picking data out of collections much easier. LINQ can save a lot of time that would otherwise be spent writing complicated for or foreach loops. As with the nature of collections in programming, sometimes you will be working with gargantuan data sets and other times with smaller yet complicated collections.

LINQ is built into the .NET framework, which most (if not all) C# developers use for server/backend development projects. It is battle-tested, and readily available for all situations. You can start using LINQ by simply adding a using directive at the top of your C# file, like this:

using System.Linq;

This article will assume you are already at least a little familiar with LINQ, as it is difficult to look at any .NET codebase and not at least see some LINQ being used. For those unfamiliar with it, I am a bit envious! It will likely improve your workflow significantly after learning even just the basics.

Before diving into our four expert-level tips, I’ll go over two super basic and useful methods included in LINQ, just to get you up to speed. For those already familiar with these, feel free to skip down to the numbered sections below.

LINQ Super Basics

Seeing that LINQ is for the management of collections, let’s make a simple collection that we can use.

public class Person
{
    public string Name {get; set;}
    public int Age {get; set;}
    public List<string> Hobbies {get; set;}
    public string FavoriteColor {get;set;}
    public string Nationality {get;set;}
}

This is the class we will be working with. Perhaps a bit too standard, but easy to understand – which is the point here.

Here is a list of people who are all persons:

    public List<Person> people = new List<Person> {
        new Person {
            Name = "Carl",
            Age = 35,
            Hobbies = new List<string>{"Tennis", "Video Games"},
            FavoriteColor = "blue",
            Nationality= "American"
            },
        new Person {
            Name = "Rebecca",
            Age = 40,
            Hobbies = new List<string>{"Collecting Stamps", "Watching Movies"},
            FavoriteColor = "blue",
            Nationality= "American"
            },
        new Person {
            Name = "Elliot",
            Age = 20,
            Hobbies = new List<string>{"Changing Lightbulbs", "Playing the Banjo", "Classical Literature"},
            FavoriteColor = "green",
            Nationality= "American"
            },
        new Person {
            Name = "Ferdinand",
            Age = 25,
            Hobbies = new List<string>{"Writing Mystery Novels", "Restoring classic vehicles", "Watching Cat Videos"},
            FavoriteColor = "red",
            Nationality= "American"
            },
        new Person {
            Name = "Veronica",
            Age = 93,
            Hobbies = new List<string>{"Poker", "Mahjong", "Daredevil stunts"},
            FavoriteColor = "purple",
            Nationality= "French"
            },
    };

This will be our list going forward. I think writing it out will help you visualize and understand why we are getting the results we are with future examples.

Where

This is a pretty basic trick, but it’s helpful. Let’s grab a list of all the Americans.

var americans = people.Where(x => x.Nationality == "American");

This is going to create a collection of the Person class defined above that will return all people who are Americans. So essentially, everyone except Veronica. Now you have a collection you can further manipulate or maybe return. A good use case for this would be getting a list of all users who have an active flag set to true from your data set.

Using LINQ’s Where allows us to do this with just one line of code. For contrast, let’s see a way you could accomplish this without LINQ using a foreach loop.

        List<Person> americans = new List<Person>();
        foreach(Person person in people)
        {
            if(person.Nationality == "American")
            {
                americans.Add(person);
            }
        }

As you can see, we reach the same conclusion using both methods, but we can cut out several lines of code by using Where instead of a foreach loop. You can use the && operator to apply additional requirements for which Persons get put into the new list.

LINQ uses something known as a Lambda operator:
(x => x.property). Here is more information on how this works.

Related Posts:  Service Highlight: .NET Development

X is just a stand-in for the variable name; it doesn’t matter what it is, so feel free to rename it if it helps with readability. You’ll see an example below where I rename X since multiple LINQ methods are chained together, and it might be confusing if I used X for everything.

Select

Here’s another one of our bread-and-butter methods. Let’s say that you don’t need the whole Person object; you just need their ages.

var ages = people.Select(x => x.Age);

This will return a list of ints that are only the ages of the people in your list. Super simple and often more performant than selecting a collection of complex objects when all you really only needed was one property anyway.

Chaining LINQ Methods

Here’s a bonus that should illustrate how powerful LINQ queries/methods can be. Let’s combine both Where and Select to get something new.

var ageOfAmericans = people.Where(x => x.Nationality == "American").Select(x => x.Age);

This is going to follow the sequence in which the methods are called. First, grab all the Americans, and then, return a list of their ages. Sorry Veronica.

This is where the true beauty of LINQ lies. You can write a single line of code that accomplishes what several foreach loops would be needed to reproduce. These are the super basics. Here is a comprehensive list of methods included with LINQ.

Now that we have the basics out of the way, let’s dive into some more complicated subjects. Without further ado, here are four tips and tricks for LINQ devs!

#1 Avoid Null Reference & Sequence Contains No Elements Errors with *OrDefault Methods

I’ve worked on many applications that run into the dreaded Null Reference Exception, and it seems that quite often, it’s related to LINQ methods running into results the application wasn’t expecting.

An example of this can be seen using the First() method:

var personWhoIs30 = people.First(x => x.Age == 30);

Console.WriteLine(personWhoIs30);

Looking at our people collection up above, are there any 30-year-olds? Nope. So what’s gonna happen? We’re gonna get a ‘Sequence contains no elements’ error when we run this code.

Well, that’s no good! How can we avoid this? Let’s use the FirstOrDefault method instead.

var personWhoIs30 = people.FirstOrDefault(x => x.Age == 30);

Console.WriteLine(personWhoIs30);

We can run this without running into an exception in code, but since our collection doesn’t contain anyone who is actually 30 years old, it’s not going to print anything. You don’t have to provide a default value, it will automatically use null as a default value. This will prevent your code from breaking on the LINQ method itself. You don’t have to, but if you want, you can choose to provide a default value.

var personWhoIs30 = people.FirstOrDefault(x => x.Age == 30, new Person {Name = "Franky"});

Console.WriteLine(personWhoIs30.Name);

OrDefault Method in LINQ

Several other methods contain OrDefault variants, including Last, Single, and ElementAt.

#2 Any vs Count For Null Checking

As with anything in programming, with enough creativity, there are endless ways to accomplish the same goal. Not all of them are equal, though, because performance is also a consideration. Let’s do some null checking in a collection.

var anyoneNamedCarlCount = people.Where(x => x.Name == "Carl").Count() > 0;

var anyoneNamedCarlAny = people.Any(x => x.Name == "Carl");

These two approaches will reach the same result: a true boolean value. These two approaches have very different performance results, though! Can you guess which one is going to be more performant?
.Any() will be a lot faster than .Count() > 0!

Why is that? Count will go through the entire collection and check to see if each person has the name Carl, so it can tell you what the count is. If you’re actually trying to get the number for practical use, that’s fine, but since we’re null-checking, we only care if there’s a single person named Carl. There is!

What Any does is it will iterate through our collection until it finds the first instance of a person named Carl, then it will break execution. In the above example, we only have a few people to iterate through, but if you have a massive data set with thousands of entries in a collection, you can probably see how using Any could save some time!

Related Posts:  Legacy Code Automation with AI: Building a Solution

#3 GroupBy, OrderBy, ThenBy

Sometimes sorting a collection is necessary, and anyone who has tried to do so with a foreach loop has probably experienced a bit of resistance. However, it’s pretty effortless with LINQ.

There are two major ways to accomplish this, one that will simply rearrange the collection as a singular collection, and one that will actually break it down into multiple collections. OrderBy is what you want when you want one large collection ordered by a singular property (by default anyway).

var favoriteColors = people.OrderBy(x => x.FavoriteColor);
foreach(Person person in favoriteColors)
        {
            Console.WriteLine(person.Name + " Age: " + person.Age + ": " + person.FavoriteColor);
        }

This will result in simply ordering the collection alphabetically by favorite color.

Now, let’s try using GroupBy to break it into multiple collections.

 var favoriteColors = people.GroupBy(x => x.FavoriteColor); foreach(var group in favoriteColors) { Console.WriteLine(group.Count() + " " + group.FirstOrDefault().FavoriteColor); } 

It’s important to emphasize that GroupBy will return a collection of collections, so you’ll need to go through a bit more effort to utilize the data effectively. This is illustrated by the example above using the FirstOrDefault() method.

These are neat methods to help you organize your data for further mutation but are still pretty basic usages. Let’s add some complexity to OrderBy in the form of ThenBy.

var favoriteColors = people.OrderBy(x => x.FavoriteColor).ThenBy(x => x.Age);
        foreach(Person person in favoriteColors)
        {
            Console.WriteLine(person.Name + " Age: " + person.Age + ": " + person.FavoriteColor);
        }

This will result in the following:

Results of OrderBy in LINQ

Wait a minute, that’s the same… That’s because when you OrderBy, it always orders by the results ascending, so A-Z and 0-9. Let’s try ThenByDescending()!

var favoriteColors = people.OrderBy(x => x.FavoriteColor).ThenByDescending(x => x.Age);
        foreach(Person person in favoriteColors)
        {
            Console.WriteLine(person.Name + " Age: " + person.Age + ": " + person.FavoriteColor);
        }

There we go! Similarly, the OrderByDescending() method also exists in case you want your first criteria to be in reverse order. You can chain as many ThenBy()s as you want! Our data set for this article is small, though, so we won’t get much practical use from that here. However, if you have a massive dataset and need to order things in a very specific way based on multiple properties, go wild!

#4 Do Math

There are a lot of built-in methods that will help you organize your data, but there are a few that can help you do math as well.

Console.WriteLine("Oldest person is: " + people.MaxBy(x => x.Age).Name);
Console.WriteLine("Youngest person is: " + people.MinBy(x => x.Age).Name);
Console.WriteLine("Collective age of people is: " + people.Sum(x => x.Age));
Console.WriteLine("Average age of people who like to watch things: " + people.Where(x => x.Hobbies.Any(hobby => hobby.Contains("Watching"))).Average(person => person.Age));

The results:

Using functions of LINQ to do Math

There’s not a lot to go over in explaining these, but as you can see, you can chain a lot of LINQ methods together to get a lot of fun mathematical results. For extra fun throw in the Math class.

Console.WriteLine("Square Root of the age of people who like to watch things: " + Math.Sqrt(people.Where(x => x.Hobbies.Any(hobby => hobby.Contains("Watching"))).Sum(person => person.Age)));

Results of math functions in LINQ

Conclusion

Many powerful uses for LINQ may not be obvious at first glance. In this post, we covered four strategies that I often employ during development, and I hope you find them just as useful. LINQ and SQL share significant overlap, meaning there’s a good chance that if you can accomplish something in SQL, you can do it in LINQ as well. By being creative with your data manipulation, you can write clean, highly readable code while also improving the performance of your existing collection operations.

If you’re interested in expanding your knowledge further, check out the Keyhole Dev Blog for more insights and practical tips on software development. Our team regularly shares articles on topics like .NET, data manipulation strategies, and best practices that can help level up your skills.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments