When I’m working on GeoBlazor or other Blazor applications, I often have to switch between C# and JavaScript/TypeScript. While the two langauges have a lot in common (C family syntax, async/await, lambdas), one place I often get confused is when dealing with arrays or collections of items. In C#, the most straightforward way to do this is with LINQ
queries. Most of these query methods work on any collection type, including Array
, List
, ReadOnlyList
, HashSet
, and the related interfaces. The root interface necessary is IEnumerable
. With ES6 and later versions, JavaScript has adopted many of these same approaches as methods on the Array
class, using Array.prototype
(prototype is the JavaScript concept of inheritance).
In this post, I am going to discuss all of the methods of IEnumerable
in LINQ
and their JavaScript equivalents. Some of these methods are very common, while others, such as the new .NET 6 ...By
methods, are less well known. I’m also not going to deal with every possible parameter overload, as many LINQ methods allow for custom IComparer
implementations or other optional parameters. In most cases, you will see that JavaScript has fewer methods than C#, but that they can be used in many of the same ways. It is also important to note that all IEnumerable
methods return a new collection, and do not modify the original input collection. C# does have mutable collection methods, for example on the List
class, such as Add
, Remove
, Sort
, but these are kept distinct from the LINQ methods to make it clearer when you are mutating the original value. In JavaScript, both mutating and functional methods exist on Array.prototype
. In fact, there is a recent collection of new methods that only return a new array, such as toSorted
, toReversed
, and toSpliced
, but these are not yet implemented in all major browsers at the time of writing. In the comparison chart, I do use several mutating array methods, but only because there is no functional equivalent in JavaScript to compare to LINQ.
If you want to skip to the comparison table to look up a method, jump to the end of this post.
In both languages, you can easily create a new collection. new List<T>()
in C#, or []
in JavaScript, for example. C# also has the following convenience static methods on IEnumerable
to generate a new collection.
Empty
- Creates a new, empty IEnumerable
instance.Range
- Creates a new collection of sequential intervals, with a given start index and count.Repeat
- Generates a new collection with the same item repeated a given Count
times.ElementAt
- Finds the element at a particular index. Similar to using an indexer (e.g., array[index]
). Throws if the index is out of range.ElementAtOrDefault
- Like ElementAt
but returns default
(null
for nullable types) if the index is out of range.First
- Finds the first item that matches the predicate. Throws on no match.FirstOrDefault
- Like First
but returns default
if no match is found.Last
- Finds the last item that matches the predicate. Throws on no match.LastOrDefault
- Like Last
but returns default
if no match is found.Max
- Returns the item with the highest numeric value.MaxBy
- Returns the item with the highest Key
value according to the given IComparer
.Min
- Returns the item with the lowest numeric value.MinBy
- Returns the item with the lowest Key
value according to the given IComparer
.Single
- Like First
, but also throws if multiple matches are found.SingleOrDefault
Like FirstOrDefault
but also throws if multiple matches are found.As you can see, each method comes in two flavors, null
-forgiving, and null
-throwing. Like Nullable Reference Types, this is useful for explicitly declaring your intentions, and not accidentally returning a null
where you really expected there to be a value. The Single
methods also verify that you have exactly one match in your collection.
at
- Finds the element at a particular index.find
- Finds the first element that matches the predicate.findLast
- Finds the last element that matches the predicate.In JavaScript, we have fewer methods that return a single value. Unlike in C#, JavaScript never throws errors if it fails to find an item. Instead, all of these methods return an undefined
value if a match is not found. There is also no equivalent of Single
in JavaScript. Instead, you would have to filter
and then throw
if you found more than one match.
Distinct
- Removes any duplicate entries and returns a collection in which each entry is unique.DistinctBy
- Removes entries with duplicate Key
values.Except
- Returns all items from the first collection that are not in the second collection.ExceptBy
- Returns all items from the first collection that do not have a matching Key
value in the second collection.Intersect
- Compares two collections and returns all items that are present in both.IntersectBy
- Compares two collections and returns all items that have matching Key
values in both.OfType<T>
- Filters the collection to only records of a particular Type
.Skip
- Skips a specified number of records, and then returns the rest of the collection.SkipLast
- Returns the collection minus the specified number of records at the end.SkipWhile
- Skips forward over the collection until the predicate is false
, and then returns the rest of the collection.Take
- Returns the first specified number of elements in the collection.TakeLast
- Returns the specified number of elements from the end of the collection.TakeWhile
- Returns all elements from the start of the collection until the predicate is false
.Where
- Finds all matches to the predicate. If none found, returns an empty IEnumerable.filter
- Finds all matches to the predicate. If none found, returns an empty array.slice
- Returns a sub-section of the collection by start index and optional end index.Once again, C# has multipe methods that can filter a collection, whereas JavaScript has just the one. Yet you can easily filter
on class type or the contents of a second array.
Order
- Sorts the items by their default comparison in ascending order.OrderBy
- Sorts the items by a Key
value in ascending order.OrderByDescending
- Sorts the items by a Key
value in descending order.OrderDescending
- Sorts the items by their default comparison in descending order.Reverse
- Returns a collection in the opposite order from the original.ThenBy
- Used to chain sorting calls with any Order
, OrderBy
or other ThenBy
call. Sorts by a new Key
ascending.ThenByDescending
- Used to chain sorting calls with any Order
, OrderBy
or other ThenBy
call. Sorts by a new Key
descending.sort
- Sorts the items by their default order or a comparison function. Mutates the original array.reverse
- Returns the array in the opposite order from the original. Mutates the original array.Append
- Adds a new item to the end of the collection.Concat
- Adds a new collection to the end of the first collection.Join
- Combines two collections, based on a defined Key
in each, and a custom function to join the two together.Prepend
- Adds a new item to the beginning of the collection.Union
- Combines two collections, excluding duplicates.UnionBy
- Combines two collections, limiting each Key
to a single instance.concat
- Adds a new array to the end of the first array. Does not alter the existing arrays.push
- Adds a new item to the end of the array, and returns the new length
.unshift
- Adds a new item to the beginning of the array, and returns the new length
.These methods return a true
or false
depending on what is in the collection.
All
- Returns true
if all items match the predicate.Any
- Returns true
if any item matches the predicate.Contains
- Returns true
if the item is in the collection.SequenceEqual
- Compares each item in two collections, and returns true
if they all match.every
- Returns true
if all items match the predicate.includes
- Returns true
if the item is in the array.some
- Returns true
if any item matches the predicate.There are many transformations possible on a collection of items, especially on numeric types. These methods can be very powerful, and are in my opinion the hardest to keep straight in terms of different names. For example, Aggregate
, and reduce
seem like opposite terms, yet they are actually the same concept! Select
and map
are probably the most common transformation in each language, so it is important to know how to use them well.
Aggregate
- Applies an Accumulator
function to the collection, where each item and the accumulation are passed as parameters. Also supports giving a starting seed
value.Average
- Finds the numeric average of the collection values.Count
- Returns the number of collection items as an int
.LongCount
- Returns the number of collection items as a long
.Select
- Transforms each item in the collection via a custom function into a new value.SelectMany
- Transforms each item in the collection into a new collection of values, which are then flattened into a single new collection.Sum
- Adds all the values together for the collection.TryGetNonEnumeratedCount
- Attempts to count the items in the collection without actually enumerating the items. Returns a boolean to indicate success, and has an out
parameter with the count value.Zip
- Uses a custom function to combine each item in two collections together by index.flatMap
- Transforms each item in the collection into a new array of values, which are then flattened into a single array.length
- Returns the number of items in the array.map
- Transforms each item in the array via a custom function into a new value.reduce
- Applies an accumulator
function to the array, where each item and the accumulation are passed as parameters. Also supports an initialValue
.These methods do not have a JavaScript equivalent because JS is a loosely-typed language. Instead, you can simply treat one type like another, and if it has the correct properties and methods, it will work. In C#, not only do you need to cast to the appropriate collection type for some usages, but since many LINQ methods use deferred execution, this also forces the query to actually run and produce an in-memory collection.
AsEnumerable
- Returns the collection as an IEnumerable<T>
.Cast<T>
- Returns the collection with each item cast to the T
value in an IEnumerable<T>
.DefaultIfEmpty
- Returns the original collection, or if the collection was empty, returns a new collection with a single, default value (e.g., null
).ToArray
- Returns a fixed-length array.ToDictionary
- Transforms a simple collection into a Key/Value pair Dictionary.ToHashSet
- Returns a collection with no duplicates, similar to Distinct
, but the HashSet
type prevents adding duplicates in the future as well.ToList
- Returns a mutable list that can be added to or removed from.ToLookup
- Groups the collection under key
lookup values.Chunk
- Creates a collection of arrays, of a fixed maximum size, from the original collection.GroupBy
- Creates a collection of IGrouping
elements, each of which is a collection of items from the original collection grouped by a predicate.GroupJoin
- Creates a new collection, normally with a different Type
, that contains the joined results of two collections.In the following chart, I aimed for a 1-1 comparison whenever possible. If there was no JavaScript Array method, I tried to find the most succinct way of achieving the same result in code.
C# LINQ IEnumerable Method |
JavaScript Array.prototype Method |
---|---|
Aggregate((acc, x) => function, seed) |
reduce((acc, x) => function, seed) |
All(x => predicate) |
every(x => predicate) |
Any(x => predicate) |
some(x => predicate) |
Append(item) |
push(item) |
AsEnumerable() |
N/A |
Average() |
reduce((acc, x) => acc + x) / array.length |
Cast<T>() |
N/A |
Chunk(size) |
no simple equivalent |
Concat(otherIEnumerable) |
concat(otherArray) |
Contains(item) |
includes(item) |
Count() |
length |
DefaultIfEmpty() |
some() ? array : undefined |
Distinct() |
[...new Set(array)] 2 |
DistinctBy(x => x.Key) |
no simple equivalent |
ElementAt(index) |
at(index) |
ElementAtOrDefault(index) |
at(index) |
Empty<T>() 1 |
[] 2 |
Except(otherIEnumerable) |
filter(x => !otherArray.includes(x)) |
ExceptBy(other, x => x.Key) |
no simple equivalent |
First(x => predicate) |
find(x => predicate) |
FirstOrDefault(x => predicate) |
find(x => predicate) |
GroupBy(x => predicate) |
no simple equivalent 3 |
GroupJoin(inner, o => o.Key, i => i.Key, func, comparer) |
no simple equivalent |
Intersect(other) |
filter(x => otherArray.includes(x)) |
IntersectBy(other, x => x.Key, comparer) |
no simple equivalent |
Join(inner, o => o.Key, i => i.Key, func) |
no simple equivalent |
Last(x => predicate) |
findLast(x => predicate) |
LastOrDefault(x => predicate) |
findLast(x => predicate) |
LongCount() |
length |
Max() |
Math.max(...array) 2 |
MaxBy(x => x.Key) |
sort((a, b) => b.Key - a.Key)[0] |
Min() |
Math.min(...array) 2 |
MinBy(x => x.Key) |
sort((a, b) => a.Key - b.Key)[0] |
OfType<T>() |
filter(x => x instanceof T) |
Order() |
sort() 4 |
OrderBy(x => x.Key) |
sort((a, b) => a.key - b.key) 4 |
OrderByDescending(x => x.Key) |
sort((a, b) => b.key - a.key) 4 |
OrderDescending() |
sort((a, b) => b - a) 4 |
Prepend(item) |
unshift(item) 4 |
Range(start, count) 1 |
[...Array(count + start).keys()].slice(start) 2 |
Repeat(item, count) 1 |
fill(item, startIndex, endIndex) 4 |
Reverse() |
reverse() 4 |
Select(x => function) |
map(x => function) |
SelectMany(x => function) |
flatMap(x => function) |
SequenceEqual(otherIEnumerable) |
no simple equivalent |
Single(x => predicate) |
filter(x => predicate); result.length > 1 ? throw error; |
SingleOrDefault(x => predicate) |
filter(x => predicate); result.length > 1 ? throw error; |
Skip(count) |
slice(count) |
SkipLast(count) |
slice(0, -count) |
SkipWhile(x => predicate) |
no simple equivalent |
Sum() |
reduce((acc, x) => acc + x) |
Take(count) |
slice(0, count) |
TakeLast(count) |
slice(-count) |
TakeWhile(x => predicate) |
no simple equivalent |
ThenBy(x => x.Key) |
no simple equivalent |
ThenByDescending(x => x.Key) |
no simple equivalent |
ToArray() |
N/A |
ToDictionary(x => function, x => function) |
reduce((acc, x, i) => acc[functionK] = functionV) |
ToHashSet() |
new Set(array) 2 |
ToList() |
N/A |
ToLookup(x => function, x => function) |
no simple equivalent |
TryGetNonEnumerated(out int count) |
no simple equivalent |
Where(x => predicate) |
filter(x => predicate) |
Union(otherIEnumerable) |
[...new Set(array.concat(otherArray))] |
UnionBy(other, x => x.Key) |
no simple equivalent |
Zip(other, (a, b) => function) |
map((a, i) => functionWithOther[i]) |
1 - static method on Enumerable
2 - not a method on Array.prototype
3 - group
method is in experimental stage
4 - mutates the original array
I hope this deep dive into LINQ functional methods and their JavaScript Array counterparts was useful. If you are a .NET Blazor developer or interested in Geospatial Information Systems (GIS), checkout GeoBlazor and the dymaptic blog for more content!