LINQ in C#
LINQ (Language INtegrated Queries) is a System Library in C# that allows you to cleanly manipulate ordered collections, and offers highly readable code, especially one-lines. If used right, it can even do so without at the cost of performance. It offers a variety of convenient methods that are usable in a wide range of applications.
All important methods are outlined in the documentation for Linq.Enumerable.
This article will likely boggle the minds of those new to LINQ. I advise looking at the various list of applications here, saving this page, and coming back to it when convenient.
Including “using
System.Linq;”
at the start of the file allows you to access these extension methods.
Essentials
IEnumerables
IEnumerables are simply lists of objects. A list of dates is an IEnumerable. A list of grocery items to buy is an IEnumerable. A list of contacts is an IEnumerable. It is, quite simply, a collection that you can iterate over.
C# Functions
A function in C# is any static or non-static method. But more importantly, you’ll see the following throughout LINQ:
Func<T1, T2, T3, T4, Tout>
It’s a function which, in this case, takes 4 inputs (of types T1, ... , T4
), and has return type Tout
. You will rarely need to explicitly specify these types, as C# will infer them on their own. If it doesn’t it should give a warning or an error stating that it’s unable to do so, and you may then write these types out explicitly.
Inside a static function, these functions may be required to be static;
Lambda Expressions
Lambda expressions are just shortened syntax for functions, whose usage is generally temporary. They can always be substituted with an equivalent function.
Their notation is simple:
([inputs]) => output
For example, to include a lambda where 2 numbers simply add each other, we write:
(x, y) => x + y
If we were defining a function, we’d write as follows:
int Sum(int x, int y) { return x + y; }
The program infers the input types of the inputs and outputs based on the types it requires. Suppose we have something like this:
int ApplyFunc(Func<int, int> func, int x, int y)
{ return func(x, y); }
We may either input the Sum method after defining it seperately:
ApplyFunc(Sum, 2, 5);
Or we may not define the Sum
method at all so we may simply one-line it:
ApplyFunc((x, y) => x + y, 2, 5);
This has the exactly the same result as before.
Finally, single-input lambdas don’t require brackets. That is, this:
(x) => x * x;
can be written as this:
x => x * x;
You’ll see this in action throughout the remainder of the article.
Various simple problems
The documentation covers various examples of each method in practice already, so there’s little need for that.
Factorials
A factorial of n
just multiplies the first n
positive integers. The factorial of 4 is 1 x 2 x 3 x 4 = 24.
public static int Factorial(int n)
=> Enumerable.Range(1, n+1).Aggregate((p, q) => p * q);
- Enumerable.Range: Gets the range of integers from
1
(inclusive) ton+1
(not inclusive) — that is,1, 2, ... , n
. The result is an IEnumerable. - Enumerable.Aggregate: Aggregates them (that is, it repeats the same function on all the elements in the range) according to the function/lambda expression
(m, n) => m * n
. To elaborate, it computes1 * 2
, takes the output2
, then computesoutput * (next input) = 2 * 3
and repeats the process until it’s covered all the elements of the ordered collection.
Enumerable.Aggregate was your first experience with a lambda expression. It took two inputs (p
and q
), and simply multiplied them together.
Note how it’s obvious that the 2 input types are both int
, and so the result is an int
since the product of 2 int
s is an int
. As a result, we didn’t have to write .Aggregate<int, int, int>((p, q) => p * q)
since the compilar could infer it on its own.
Sum of Prices of all the Transactions in an Invoice
Aggregating addition of integers or floats over addition is functionally equivalent to the Enumerable.Sum
methods.
Suppose first that have the following preliminary code:
public class Invoice { public List<Transaction> transactions; }
public class Transaction { public float price; }
We’ll compute the sum of prices two ways: using Enumerable.Aggregate and Enumerable.Sum.
Using Aggregate:
invoice.transactions.Aggregate((x, y) => x.price + y.price);
Using Sum:
invoice.transactions.Sum(x => x.price);
Both of these give the same output. However, the Sum
look much cleaner than Aggregate
here.
Note the lack of brackets in the single-input lambda expression in the last code block.
List of all the Prices of Transactions in an Invoice
What if we want to do other things with the prices, and not sum them up?
The Enumerable.Select method allows you to take all the elements in a list, and output a different list of elements based on the original list. The resulting list’s elements may be of the same type or different.
Now, to get a list of all the prices:
IEnumerable<int> prices = invoice.transactions.Select(t => t.price);
One (not ideal) way to sum all the prices:
int sum = invoice.transactions.Select(t => t.price).Sum();
However, to sum the numbers, there’s no need to gather them through Enumerable.Select first, as you may use Enumerable.Sum directly:
int sum = invoice.transactions.Sum(t => t.price);
Minimum or Maximum of a Collection of Numbers
There’s a good chance you’ve already seen the Math.Min
and Math.Max
methods, but even more powerful are the Enumerable.Min and Enumerable.Max methods.
If you have a list of numbers (integers, floats, doubles, etc.), you can simply call Enumerable.Min or Enumerable.Max to get their maximum or minimum:
List<int> list = new List<int> {2, 5, -12};
int min = list.Min();
int max = list.Max();
But what if you have an array of integers? For that, we simply use Enumerable.Cast<int> to convert it to IEnumerable
:
int[] arr = {2, 5, -12};
int min = arr.Cast<int>().Min();
int max = arr.Cast<int>().Max();
Replace all Vowels with Underscores
First, let’s write an expression to determine if a character ch
is a vowel or not. The following code is simple enough:
"aeiouAEIOU".Contains(ch);
In C#, strings inherit IEnumerable<char>, so we can still manipulate theem with LINQ.
string replaced = (string)str.Select(ch =>
{ if ("aeiouAEIOU".Contains(ch) return '_'; else return ch; });
I used curly braces here for the lambda since my code required more than one statement (since there are 2 semicolons instead of 1).
The above code shouldn’t be difficult to read: if it’s a vowel, return a _
at that index, otherwise return the character itself. We need to convert it back to a string since string
inherits IEnumerable<char>
, not the other way around.
Since this is string manipulation, this may be done with a little regex. But I’m explaining LINQ here, not regex.
Grouping a List of Players in a Game by their Class
You’ve heard of classes in role playing games (if you’ve played them). If you haven’t, most games have at least 4 types: Warriors, Archers, Mages, Healers.
Suppose your preliminary code looked something like this:
public enum Class { Warrior, Archer, Mage, Healer }
public class Player { ... public Class PlayerClass; ... }
We don’t want to simply use Enumerable.Select, since we want to actually group them together. This is convenient if I want to simply see who is an archer right now, and later, maybe look at who the mages are.
To group, we simply us Enumerable.GroupBy:
playerList.GroupBy(p => p.PlayerClass);
This returns an IEnumerable<IGrouping<Class>>. It’s a new list of “groupings”. Each of these groups has something called a “Key”. The keys here will be what you expect them to be: the classes of the players in the list of players for the game.
But how do we know who is in the grouping? Simple, IGrouping<T> is also an IEnumerable, so we can iterate over its elements as well. And the IGroupings themselves contain the players with the given class as IGrouping’s key.
To get all the mages as a list, we can do:
playerList.GroupBy(p => p.PlayerClass) // Group by classes
.Single(g => g.Key == Class.Mage) // Gets mage grouping
Enumerable.Single just gets the lone element in the group satisfying the condition. If there are none or more than one, it throws and exception.
There is, however, an easier way to retrieve all the mages in the player list:
playerList.Where(p => p.PlayerClass == Class.Mage);
This just retrieves all the elements in the IEnumerable that have PlayerClass of Mage.
In Conclusion
When confused, just check the documentation for Enumerable and click on the “Methods” dropdown on the left side to see its list of methods. I advise that you do some challenges on Code Wars and read other people’s solutions to observe LINQ in practice.
LINQ is a very powerful tool. I covered as many as I could for one day, and hopefully, you’ll learn more about the advantages of LINQ. Remember, you need using System.Linq
to use its extension methods.