LINQ - (Language Integrated Query) is an absolute gem of a tool available in C#. It works the same as SQL does and allows you to perform querys on objects within your code.
This can be done in two ways. You can use the language-level query syntax, or the LINQ API.
Linq Api - Language Level Query Syntax
You may see in code something that resembles a SQL, like this :
var animals = new List<Animal>{ new Animal{Name="Dog", Age= 2, Sound="Bark"},
new Animal{Name="Cat", Age=2, Sound="Meow",
new Animal{Name: Fox, Age=5, Sound="Bark"}
var barkingAnimals =
from animal in animals
where animal.Sound == "Bark"
select animal.
You can build a query just like you would with other query languages such as SQL. This query language can be useful for building complex queries, but in this example it could be deemed excessive. We can simplify this select statement using the LINQ API methods
Linq API - Method Syntax
The LINQ API methods utilises a predicate (in the form of a lambda extension) to determine the criteria, (you can learn about these in my previous module.
So let's look at how we could re-write
const barkingAnimals = animals.Where(x=> x.Sound == "Bark").ToLis();
It's a lot easier to write and much more concise for such a simple filter.
I find it reads a lot better too,
"barking Animals equals all the animals where the sound property equals Bark"
Why do we add ToList()
on the end?
Linq querying methods all returns an IQueryable
object. This informs the compiler that the variable is not the result of the query, but the definition of the query, see deferred execution later in this article.
In order to be able to use the results of the query we can either:
iterate over the queryable object (e.g using a ForEach loop)
convert to an IEnunerable type, e.g a List / Array.
Common LINQ API methods
OrderBy
OrderBy is a very useful LINQ Api method which simply allows us to order any object that inherits from IEnumerable, for example Lists or Arrays.
We can use it like so:
var orderedByAge = people.OrderBy(x=>x.Age);
// or
var orderedByAgeDescending = people.OrderByDescending(x=>x.Age);
the above shows examples of ordering by Age in both ascending and descending order.
First()
var first = animals.First(x=> x.Sound == "Bark");
This will return the first object from the list that matches the criteria.
Single / SingleOrDefault
This is used when you know there will only ever be / or you expect there only to be one object that matches your criteria.
Example :
var cat = animals.Single(x=>x.Name == "Cat");
However, we all know that data can change over time, or 3rd party services could return different data than what was expected. It is always best to write defensive code where possible.
If we were to call the above code, and it finds there are actually more than one object with the name "Cat", it would throw an uncaught error (this means we're not expecting nor handling the error correctly).
To prevent this, we can use the SingleOrDefault
method. This method will handle the error for us, and return the default value on error , so therefore cat
would equal null
because the default value for a string is null
. We can then check within the code that the cat variable is not null:
var cat = animals.SingleOrDefault(x=> x.Name=="Cat");
if(cat != null){
Console.WriteLine("A single cat was found");
}
I will teach how to handle errors, in another article.
Select
Say we wish to return only the types of animal,i.e we don't need all the other information.
If so we can use the Select
method. This will create a new object for each element in the list / array.
Example:
var typesOfAnimal = animals.Select(x=>x.Name).ToList();
But suppose we want to have the name and there sound? That's just as easy with the Select method, however this time we'll create an anonymous object instead of just returning the property.
var animals = animals.Select(x=> { Name = x.Name,
Noise = x.Sound}).ToList();
You can see a full list of available methods on the Microsoft site.
Combining methods
Another filtering extension method which is available on the IEnumerable types is the Contains
method. This method does exactly what you'd expect, it returns all the items which contain the given predicate.
For example, say we have a list of People. And we want to find all the people who's address contains "United Kingdom".
var people = new List<People>(){
new() {Name="John Doe", Address="123 Privet Drive, Hogwarts, United Kingdom"},
new() {Name="Alex the Kidd", Address= "Rock Paper Scissors Avenue, United Kingdom"},
new(){Name= "Donkey Kong", Address=" The Monkey Temple, Jungle"}
}
We could utilise a combination of the .Where()
and the Contains()
to achieve this like below:
var ukResidents = peopleList.Where(p => p.Address.Contains("United Kingdom")).ToList();
var ukResidents = people.
Deferred Execution
LINQ queries use what's called deferred execution, meaning that the query is not executed immediately when it is defined.
Instead, it is executed when the query results are iterated or when certain operators trigger the execution explicitly. This deferred execution enables optimisations and improves performance by avoiding unnecessary computations.
This goes back to what I discussed earlier with the conversion of IQueryable to, for example a List using .ToList()
. It's the to list that converts it to a list and executes the query.
Lets take a look at an example:
//first declare a list of people again
var people = new List<People>(){
{Name="John Doe", Address="123 Privet Drive, Hogwarts, United Kingdom"},
{Name="Alex the Kidd", Address= "Rock Paper Scissors Avenue, United Kingdom"},
{Name= "Donkey Kong", Address=" The Monkey Temple, Jungle"}
}
// then we'll define the query
var peopleCalledJohnQuery = people.Where(x=>x.Name == "John");
// this will create the query object, and if you hover over peopleCalledJohnQuery
// intellisense will inform you that the variable is of type _IQueryable_
Console.WriteLine("Just log out something random");
// now convert the IQueryable query object to a List, thus invoking the actual query.
var list = peopleCalledJohnQuery.ToList();
Although a very basic example, this example illustrates you can create a queryable object, execute more code, before actually executing why look the query by running the .ToList()
extension method.
Chaining IQueryable Api methods
Like the combining of Where and Contains we used earlier. You can go one step further by Chaining LINQ API methods.
Let's take the Where()
, and GroupBy()
methods.
This would allow us to filter some of the data, and then group the data by a property.
So let's put everything we've learnt within this article to use, and chain the two methods.
However instead of creating a new variable using the .ToList()
method to execute and give us a list of results we can just utilise the query object within a ForEach loop, this will execute the query and allow us to loop over the results at the same time.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Person> people = new List<Person>
{
new Person { Name = "John", Age = 30 },
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Bob", Age = 30 },
new Person { Name = "Charlie", Age = 25 },
new Person { Name = "Eve", Age = 35 }
};
var groupedPeople = people
.Where(p => p.Age >= 30) // Filter people with age >= 30
.GroupBy(p => p.Age); // Group people by their age
foreach (var group in groupedPeople)
{
Console.WriteLine($"Age Group: {group.Key}");
foreach (var person in group)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
Console.WriteLine();
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
And there you have it, a brief introduction to C# LINQ. Have a play around and see what you can achieve with this powerful tool.
You can find some useful examples on the Microsoft website here
Feel free to give me a follow here and on Twitter for updates on new articles, or to ask any questions.