C# vs JavaScript: LINQ
This post belongs to the C# vs JavaScript series.by Nicklas Envall
If you've used JavaScript for some time, then you probably have gotten used to using higher-order functions such as reduce
, map
, and filter
. So it can be de-motivating to find that many C# examples on the web use loops or conventional static methods.
But don't give up hope on C# just yet, because we have System.Linq
.
In LINQ, we have sequences and elements. Any object that implements IEnumerable<T>
is a sequence, and any item within a sequence is an element. We use methods known as query operators to transform sequences, which take an input sequence and outputs a transformed output sequence. They return the new sequence without altering the input sequence.
There are three different types of query operators:
- Sequence in and sequence out.
- Sequence in and element or scalar value out.
- Nothing in and sequence out (generation methods)
LINQ provides us with so-called standard query operators that we may use. It provides us with methods for filtering, aggregation, concatenation, and much more. We'll now compare C# LINQ with JavaScript by looking at commonly used methods.
Sequence in and sequence out
Our map
, filter
, and slice
functions are also available in LINQ. But with different names, so let us take a look.
Map in LINQ
Array.prototype.map
calls a function on every element in an array, and the result of that gets returned as a new array:
const numbers = [1, 2, 3, 4, 5, 6]; const squaredNumbers = numbers.map(val => Math.pow(val, 2)); squaredNumbers; // [1, 4, 9, 16, 25, 36]
JavaScript's map
function is equivalent to LINQ's Select
method:
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; var squaredNumbers = numbers.Select(val => Math.Pow(val, 2)); Console.WriteLine(String.Join(", ", squaredNumbers)); // 1, 4, 9, 16, 25, 36
Filter in LINQ
Array.prototype.filter
creates a new array and keeps all the elements that return true on the provided function when passed to it:
const numbers = [1, 2, 3, 4, 5, 6]; const oddNumbers = numbers.filter(val => val % 2 !== 0); const evenNumbers = numbers.filter(val => val % 2 === 0); oddNumbers; // [1, 3, 5] evenNumbers; // [2, 4, 6]
JavaScript's filter
function is equivalent to LINQ's Where
method:
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; var oddNumbers = numbers.Where(el => el % 2 != 0); var evenNumbers = numbers.Where(el => el % 2 == 0); String.Join(", ", oddNumbers); // 1, 3, 5 String.Join(", ", evenNumbers); // 2, 4, 6
Slice in LINQ
Array.prototype.slice(start, end)
takes a start or start and end index, which represents what portion of the array that we want to recreate:
const numbers = [1, 2, 3, 4, 5, 6]; numbers.slice(0, 3); // [1, 2, 3] numbers.slice(3); // [4, 5, 6] numbers.slice(2, 5); // [3, 4, 5]
In LINQ, we have both Take
and Skip
that we can use separately or in combination by chaining:
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; numbers.Take(3); // 1, 2, 3 numbers.Skip(3); // 4, 5, 6 numbers.Skip(2).Take(3); // 3, 4, 5
Sequence in and element or scalar value out
Our reduce
, some
, every
, and includes
functions are also available in LINQ. But yet again with different names.
Reduce in LINQ
To find the sum of all even numbers in an array, we could do the following in JavaScript with Array.prototype.reduce
:
const numbers = [1, 2, 3, 4, 5, 6]; const sumOfEvenNumbers = numbers.reduce((sum, number) => number % 2 === 0 ? sum + number : sum, 0) sumOfEvenNumbers; // 12
The equivalent solution in C# would be with the Aggregate
method. As we see, it has an initial accumulator value which is 0
, and that gets referred to as the seed value.
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; var sumOfEvenNumbers = numbers.Aggregate(0, (sum, number) => number % 2 == 0 ? sum + number : sum); sumOfEvenNumbers; // 12
Some in LINQ
Array.prototype.some
returns true if the provided predicate returns true on any element, else it returns false.
const numbers = [1, 2, 3, 4, 5, 6]; const oddNumberExists = numbers.some(el => el % 2 !== 0); oddNumberExists; // true
JavaScript's some
function is equivalent to LINQ's Any
method:
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; bool oddNumberExists = numbers.Any(el => el % 2 != 0); Console.WriteLine(oddNumberExists);
Every in LINQ
Array.prototype.every
returns true if all functions returns true, else false:
const numbers = [1, 2, 3, 4, 5, 6]; const onlyOddNumbers = numbers.every(el => el % 2 !== 0); onlyOddNumbers; // false
JavaScript's every
function is equivalent to LINQ's All
method:
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; bool onlyOddNumbers = numbers.All(el => el % 2 != 0); Console.WriteLine(onlyOddNumbers);
Includes in LINQ
To see if an array contains something, we can use Array.prototype.includes
in JavaScript:
const numbers = [1, 2, 3, 4, 5, 6]; numbers.includes(2); // true numbers.includes(9); // false
JavaScript's includes
function is equivalent to LINQ's Contains
method:
var numbers = new int[] { 1, 2, 3, 4, 5, 6 }; numbers.Contains(2); // True numbers.Contains(9); // False
Nothing in and sequence out (generation methods)
To create an array in JavaScript "out of thin air," may use Array.from
:
const numbers = Array.from({ length: 5 }, (_, i) => i + 1); numbers; // [1, 2, 3, 4, 5]
In C#, we have Enumerable.Range(Int32, Int32)
which allow us to generate a sequence of numbers based on a range:
var numbers = Enumerable.Range(1, 5); String.Join(", ", numbers); // 1, 2, 3, 4, 5
LINQ Gotchas
There are also some "gotchas" to be aware of when working with LINQ.
Deferred Execution
Be aware of deferred execution:
List<string> names = new List<string> { "james" }; var upperCasedNames = names.Select(name => name.ToUpper()); names.Add("jennifer"); String.Join(", ", upperCasedNames); // JAMES, JENNIFER
The reason for this deferred execution is that most query operators execute when they are enumerated, not constructed. Though, not in all cases, like with Count
:
List<string> names = new List<string> { "james" }; int length = names.Where(name => name.StartsWith("j")).Count(); names.Add("jennifer"); length; // 1
You can also use methods like ToList()
and ToArray()
to get a collection while also executing the query right away.
Subqueries
Subqueries are queries contained within another query's lambda expression. Sometimes you'll want to use queries within queries (subqueries). Though be aware that the subquery .Max()
in the example below gets recalculated each iteration of the .Select()
query:
var numbers = new int[] { 1, 2, 3 }; var numbersMultipliedMaxValue = numbers.Select(x => x * numbers.Max()); Console.WriteLine(String.Join(", ", numbersMultipliedMaxValue)); // 3, 6, 9
You avoid this by moving the subquery out of that query (if applicable), so it's no longer a subquery:
var numbers = new int[] { 1, 2, 3 }; var numbersMax = numbers.Max(); var numbersMultipliedMaxValue = numbers.Select(x => x * numbersMax); Console.WriteLine(String.Join(", ", numbersMultipliedMaxValue)); // 3, 6, 9