Comparing Underscore.js to LINQ
Overview
When LINQ was added to C# in ASP.Net 3.0, it changed the way that we work with data collections. When working with server-side data in web-based applications, LINQ is an invaluable time saver. LINQ makes short work of iterative functions by means of a simple syntax that is easy to read and understand.
Unfortunately, server-side data is no longer enough. Users now expect the sort of fast and fluid web pages that require client side processing. To be able to work with data on the client-side, you need to be comfortable with writing JavaScript. If you don’t have a good understanding of JavaScript, the transition from writing server side C# to JavaScript can be challenging.
If you are already familiar with LINQ with C#, then it would help to be able to use a similar syntax with client-side data access. Underscore.js allows you to work in JavaScript with arrays and object-collections in a similar way to LINQ, but how similar? Let’s look at this in a bit more detail.
About Underscore.js
Underscore is an open source functional programming utility library for JavaScript. Underscore provides many useful functions for working with arrays or collections of JavaScript objects, including filtering, sorting and querying.
Using underscore is as simple as adding the JavaScript file to your project and referencing it on the page.
1 2 |
<!-- underscore --> <script src="/underscore-min.js"></script> |
You can get the library file via NuGet, CDN or by downloading the file directly from the home page.
Usage
Underscore offers two different syntax styles, a functional syntax and an object-oriented syntax. If you were introduced to JavaScript by writing jQuery, then the object-oriented style might be a more comfortable choice because it follows a similar approach. The two are operationally equivalent, so picking which style to use is a matter of personal preference.
1 2 3 4 5 |
//Functional syntax _.all(articles, function(article) { return article.isRead }); //OOP Syntax _(articles).all(function(article) { return article.isRead }); |
LINQ also has several syntax choices with its declarative query and functional (a.k.a. method chaining) syntaxes, but LINQ’s declarative query syntax has no equivalent in Underscore whereas both of Underscores styles are more representative of the functional syntax found in LINQ.
1 2 3 |
//LINQ functional or method chaining syntax //Similar to both Underscore styles articles.All(article => article.IsRead); |
Underscore shares with LINQ an ability to iterate through a dataset and produce equivalent results but it is implemented in a very different way. While LINQ generally returns an IEnumerable
<T>
,
Underscore will simply return the results as an array or array of objects. Because Underscore generally returns array values rather than an object it doesn’t provide method chaining by default, instead a special chain
function must be called to wrap the results in a chainable API.
Several Underscore functions are almost identical in operation to LINQ, such as Any
and, All
and SortBy
(OrderBy
). While other functions like Select
and Where
aren’t used in the same way although they are in LINQ.
Let’s take a look at each of these functions in both C# and JavaScript to see how both are implemented and compare the similarities between them.
Any
Any
which also goes by the alias of some
in Underscore, will determine if any element in a list passes a test specified by the iterator of the function. Both LINQ and Underscore return a Boolean value and the enumeration is stopped once the test becomes true
. You can see in the example below how both Any
functions are used, the similarities are very close in either environment.
In the following example a list of articles is checked to see if the set contains any unread articles.
Underscore:
1 2 3 4 5 6 7 8 9 10 |
//Syntax _.any(list, [iterator]) //returns boolean _(list).any([iterator]) //returns boolean _.some(list, [iterator]) //returns boolean _(list).some([iterator]) //returns boolean //Example: Check to see if the user has unread articles function hasUnreadArticles(articles) { return _(articles).any(function (article) { return article.isRead}); } |
LINQ:
1 2 3 4 5 6 7 8 |
//Syntax public static bool Any<TSource>(this IEnumerable<TSource> source) //Example: Check to see if the user has unread articles public bool HasUnreadArticles(List<Article> articles) { return articles.Any(article => !article.IsRead); } |
All
All
,
which also goes by the alias of every
in Underscore, will determine if all element in a list passes a test specified by the iterator of the function. All
will short-circuit when the outcome has been determined in both LINQ and Underscore. All
is the logical opposite of Any
.
In the following example, a list of articles is checked to see if all of the articles in the set have been read.
Underscore:
1 2 3 4 5 6 7 8 9 10 |
//Syntax _.all(list, [iterator]) //returns boolean _(list).all([iterator]) //returns boolean _.every(list, [iterator]) //returns boolean _(list).every( [iterator]) //returns boolean //Example: Check to see if the user has read all of the articles function hasReadAllArticles(articles) { return _(articles).all(function(article) { return article.isRead}); }; |
LINQ:
1 2 3 4 5 6 7 8 |
//Syntax public static bool All<TSource>(this IEnumerable<TSource> source) //Example: Check to see if the user has read all of the articles private bool HasReadAllArticles(List<Article> articles) { return articles.All(article => article.IsRead); } |
Each
With Underscore, ForEach
loops can be simplified by using the each
function. The each
function will iterate through a list and perform an operation on each item in the set. There is no equivalent function in LINQ however there is a ForEach
method that is part of the List
object which operates much like its Underscore counterpart.
In the following example each we use the each
and ForEach
functions to mark all of the articles in a collection as read.
Underscore:
1 2 3 4 5 6 7 8 9 |
//Syntax //_.each(list, iterator) //_(list).each(iterator) //Example: Mark all articles as read function markAllAsRead(articles) { _(articles).each(function (article) { article.isRead = true; }); return articles; }; |
List<T> (System.Collections.Generic)
1 2 3 4 5 6 7 |
//Syntax //public void ForEach(Action<T> action) public void MarkAllAsRead(List<Article> articles) { articles.ForEach(a => a.IsRead = true); } |
SortBy
You can sort lists of arrays or objects by using the SortBy
function of Underscore. SortBy
will return a copy of the list sorted in ascending order, so you will assign the return value of the function. SortBy
can use an iterator, or property name of an object can be used by passing in the string name of an objects property. Although SortBy
works similar to LINQs OrderBy
method, there is no Underscore equivalent to get a reversed sort. Instead the results must be reversed using the JavaScript reverse
function, whereas with LINQ you would use the OrderByDecending
method.
In the examples below, a list of articles is sorted by author name in both ascending and descending order.
Underscore:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Syntax _.sortBy(list, iterator [property name]) _(list).sortBy(iterator [property name]) //sort a list of articles by the author property function sortArticlesByAuthors(articles) { return _.sortBy(articles, "author"); }; //sort a list of articles by the author property in decending order function sortArticlesByAuthors(articles) { return _.sortBy(articles, "author").reverse(); }; |
LINQ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//Syntax public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector ) //sort a list of articles by the author property private List<Article> SortArticlesByAuthor(List<Article> articles) { return articles.OrderBy(article => article.Author).ToList(); } //sort a list of articles by the author property in decending order private List<Article> SortArticlesByAuthor(List<Article> articles) { return articles.OrderByDecending(article => article.Author).ToList(); } |
Select
The select
function in Underscore is vastly different from the one in LINQ. The S
elect
method in LINQ is used for projections, the operation of transforming an object into a new model that represents a subset of data for a specific use or view. Underscore’s select
,
however, is a filtering or restriction operator that returns a restricted result set of objects to satisfy a specified condition. This is why select
in Underscore also goes by the alias of filter
, since the function returns a filtered result set.
Because the select
function of Underscore is a restriction operator, we will have to compare it to a similar restriction operator in LINQ. For this comparison, LINQ’s Where
method is actually quite similar.
In the example below, Underscore and LINQ are used to filter a list of objects. In this case we filter a list of articles that fall within a specified date range. It’s important to remember that the result set that is returned is of the same object structure that the function was initially given.
Underscore:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//Syntax _.select(list, iterator) _.filter(list, iterator) _(list).select(iterator) _(list).filter(iterator) //Returns articles within a given date range function getArticlesByDateRange(articles, beginDate, endDate) { return _(articles).select(function (article) { var articleDate = Date.parse(article.pubDate); return articleDate >= beginDate && articleDate <= endDate; }); }; |
LINQ:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Syntax public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate ) //Returns articles within a given date range private List<Article> GetArticlesByDateRange(List<Article> articles, DateTime beginDate, DateTime endDate) { return articles.Where(article => DateTime.Parse(article.PubDate) >= beginDate && DateTime.Parse(article.PubDate) <= endDate).ToList(); } |
Where
Using the where
function in Underscore is similar in some respects Where
in LINQ. In both LINQ and Underscore, where
is a restrictive operator that returns a filtered set of data. The where
function in Underscore is limited to one or more exact key-value pair matches, although LINQ is able to test a value by virtually any criteria. If you need more than key-value pair matches then the select or filter function should be used instead.
In the example below Underscore and LINQ are used to filter a set of data. In this case we filter a list of articles where the IsRead property has been set to false.
Underscore:
1 2 3 4 5 6 7 8 |
//Syntax _(list).where(properties) _.where(list, properties) //Returns unread articles function getUnreadArticles(articles) { return _(articles).where({"isRead": false}); }; |
LINQ:
1 2 3 4 5 6 7 8 9 |
//Syntax public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) //Returns unread articles private List<Article> GetUnreadArticles(List<Article> articles) { return articles.Where(article => article.IsRead == false).ToList(); } |
Map
The map function in Underscore is used for projections and is comparable to LINQ’s Select method. The result of map is a new array of values mapped by the iterators function.
In the following examples, map and Select are used to transform a set of larger article objects into a set of simplified article preview objects. The article preview contains the article’s title, author and description which is truncated to 160 characters (including the ellipsis). The transformation here is fairly simple, however we are not restricted to only simple operations and the iterator could be designed to do almost any operation.
Underscore:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Syntax _(list).map(iterator) _.map(list, iterator) function getArticlePreviews(articles) { return _(articles).map(function (a) { return { 'title': a.title, 'author': a.author, 'shortDescription': a.description.substring(0, 157) + "..." } }) }; |
LINQ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//Syntax public static IEnumerable<TResult> Select<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector ) private List<ArticlePreview> GetArticlePreviews(List<Article> articles) { return articles.Select(article => new ArticlePreview() { Title = article.Title, Author = article.Author, SortDescription = article.Description.Length > 157 ? article.Description.Remove(157) + "..." : article.Description }).ToList(); } |
Chain
Underscore’s functions, unlike LINQ, do not use fluent method-chaining by default. This is because Underscore returns a collection of objects whereas LINQ methods return an IEnumerable
object. To enable chaining in Underscore the chain
function can be called. Beginning an operation with chain
will cause the return value to be a wrapped object. When the chain is complete then the value
method can be called to return the final result.
Another significant difference between chaining in Underscore versus LINQ is how each method is executed. LINQ uses deferred execution, meaning that commands are stored in the result until, and not executed until, the result is iterated over in a For Each
loop or ToList
is called. When chaining in Underscore, every function call immediately executes and iterates through the data set returning a value. The value is then wrapped in an object to continue the chain.
The following example uses chaining to create a view based on a collection of articles. To do this, we will chain the where
, map
, and sortBy
functions together. First the list of articles is filtered for items that have not been read, and then a simplified projection is created. Finally, the items are sorted by author name and returned.
Underscore:
1 2 3 4 5 6 7 8 9 10 11 12 |
function getReadingList(articles) { return _.chain(articles) .where({ "isRead": false }) .map(function (a) { return { 'title': a.title, 'author': a.author, 'shortDescription': a.description.substring(0, 157) + "..." } }) .sortBy("author").value(); }; |
LINQ:
1 2 3 4 5 6 7 8 9 10 11 12 |
public List<ArticlePreview> GetReadingList(List<Article> articles) { return articles.Where(article => !article.IsRead) .Select(article => new ArticlePreview() { Title = article.Title, Author = article.Author, SortDescription = article.Description.Length > 157 ? article.Description.Remove(157) + "..." : article.Description }) .OrderBy(item => item.Author).ToList(); } |
LINQ |
Underscore |
Operator Category |
Any |
any |
Quantifier |
All |
all |
Quantifier |
Each (not LINQ) |
forEach |
Iterator |
OrderBy |
sortBy |
Ordering |
Where |
where (when value is an exact match) |
Restriction |
Where |
filter or select |
Restriction |
Select |
map |
Projection |
— |
chain | value |
Utility |
ToList |
— |
Conversion |
Conclusion
Underscore is a great utility for working with sets of data in JavaScript. There are a few cases when a function name from Underscore doesn’t perform the same task as it does in LINQ, but this is rare. It is important to be mindful of their different implementations with respect to chaining and deferred execution. Even though Underscore isn’t identical to LINQ, they are close enough in operation that the learning curve for Underscore is very minimal.
Load comments