Array.prototype for .NET developers
posted in javascript on • by Wouter Van SchandevijlThe Array.prototype
functions have been available for a long time but
it’s since Arrow functions
that “Linq lambda syntax”-like functionality is available in JavaScript.
This blog post explains the most common functions by comparing them to their C# equivalents.
A basic example:
// JavaScript
const result = [0, 1, 2, 3, null]
.filter(x => x !== null)
.map(x => x * 10)
.sort((a, b) => b - a);
expect(result).toEqual([30, 20, 10, 0]);
// C#
var result = new int?[] {0, 1, 2, 3, null}
.Where(x => x != null)
.Select(x => x * 10)
.OrderByDescending(x => x)
.ToArray();
Whenever you want to write a for
to loop over an array, the Array.prototype
functions
probably allow you to achieve the same result but in more functional and succinct manner.
Do note that the deferred execution we know from Linq does not apply to Array.prototype
!
Comparison Table
C# | JavaScript | MDN Link |
---|---|---|
Select() | map((cur, index, array): any) | map |
Where() | filter((cur): boolean) | filter |
Contains() | includes(value, fromIndex) | includes |
FirstOrDefault() | find((cur): boolean): undefined | any | find |
All() | every((cur): boolean): boolean | every |
Any() | some((cur): boolean): boolean | some |
Concat() | concat(arr: any[]): any[] | concat |
Skip(start).Take(start - end) | slice(start = 0, end = length-1) | slice |
string.Join() | join(separator = ‘,’) | join |
Array.IndexOf() | findIndex((cur): boolean): -1 | number | findIndex |
Count() | length: number | length |
Extension method | forEach((cur): void): void | forEach |
Mutating in JS | These are in place operations | |
OrderBy() | sort((a, b): number) | sort |
Reverse() | reverse() | reverse |
Commonly used methods
Select = map
// C#
var result = enumerable.Select(x => x);
// JavaScript
let result = array.map(x => x);
// Using spread to avoid mutation
const input = [{k: 1, v: true}, {k: 2, v: false}];
const result = input.map(x => ({...x, v: !x.v}))
When mapping to an object without code block, you need to wrap your object between extra parentheses like
[0, 1].map(x => ({value: x}));
// Because without the extra parentheses
[0, 1].map(x => {value: x});
// --> [undefined, undefined]
// is the same as writing:
[0, 1].map(x => {
value: x; // No error, because JavaScript?
return undefined;
});
Where, Distinct = filter
Where
and filter
behave pretty much exactly alike.
Linqs Distinct
we’ll need to implement ourselves.
const input = [0, 0, 1, 5, 5];
// Equals true when the first occurrence of the value is the current value
const result = input.filter((element, index, array) => array.indexOf(element) === index);
// Or: [...new Set(input)];
expect(result).toEqual([0, 1, 5]);
Aggregate, GroupBy, … = reduce
Linq has Sum, Min, Max, Average, GroupBy, etc.
While JavaScript doesn’t have them, they can all be achieved trivially with reduce
Sum, Min, Max, Average:
const input = [0, 1];
const sum = input.reduce((total, cur) => total + cur, 0);
const average = sum / input.length;
const min = Math.min.apply(Math, input); // Old school
const min = Math.min(...input); // Using spread
Aggregate, GroupBy:
const input = [0, 1, 2, 3];
const result = input.reduce((acc, cur) => {
if (cur % 2 === 0) {
acc.even.push(cur);
} else {
acc.odd.push(cur);
}
return acc;
}, {even: [], odd: []);
expect(result).toEqual({even: [0, 2], odd: [1, 3]});
slice
Linq has First, Last, Skip, SkipLast, SkipWhile, Take, TakeLast, TakeWhile.
JavaScript has slice
.
// Shallow copy
const input = [0, 1, 2, 3];
expect(input.slice()).toEqual([...input]);
// Signature
slice(startIndex = 0, endIndex = length-1);
Mutations
These functions operate in place.
OrderBy = sort
- Do a
slice()
first if you need a different array reference for the result of the sort. - Without compareFn the array is sorted according to each character’s Unicode code point value.
- The compareFn should return a number:
-1
(or any negative number):a
comes beforeb
0
: Equal1
(or any positive number):a
comes afterb
// Numbers
[10, 5].sort(); // [10, 5]: each element is converted to a string
[10, 5].sort((a, b) => a - b); // [5, 10]
// Strings
['z', 'e', 'é'].sort(); // ['e', 'z', 'é']
['z', 'e', 'é'].sort((a, b) => a.localeCompare(b)); // ['e', 'é', 'z']
// Dates
[d1, d2, d3].sort((a, b) => a.getTime() - b.getTime());
C#
- The signature is a bit different:
OrderBy(Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
OrderBy
vsOrderByDescending
: switcha
andb
…
forEach
While forEach
is not really doing any mutation by itself, it is often what it’s used for.
Mutations are especially dangerous in for example a Redux environment where UI changes might lag.
The same can usually be achieved with map
.
const input = [{value: 1, visited: 0}, {value: 2, visited: 0}];
// Potentially dangerous
input.forEach(el => {
el.visited++;
});
// ...el will create a shallow copy only!
const result = input.map(el => ({...el, visited: el.visited + 1}));
// Or use the good old loop?
for (let itm of input) {
console.log(itm.value);
}
Other mutators
reverse()
push(el1, [el2, ...])
: Add element(s) at the end. Returns the new array length.shift()
: Remove the first element at the start. Returns the removed element.unshift(el, [el2, ...])
: Add element(s) at the start. Returns the new array length.splice(start, [deleteCount, [el1, [el2, ...]]])
- The swiss army knife: add and/or remove element(s)
- Returns array of removed elements (or empty array)
-
msn0/mdn-polyfills : Include polyfills for your favourite functions that are not (yet) implemented by your browser(s).
- 2ality.com: Blogging on ES proposals
- es6-features.org: Overview and Comparison
- StackOverflow: Jon Skeet on why there is no forEach in Linq
- MooTools: Why Contains is called includes in JavaScript