LINQ and the art of clarity
After fifteen years of writing C#, I have a small confession: I think LINQ is one of the most under-rated language features of any modern language. Not because of what it can do — though that is a long list — but because of what it lets you delete.
Here's a real example from our codebase. The original:
var followedIds = new List<int>();
foreach (var f in db.Follows)
{
if (f.UserId == userId)
followedIds.Add(f.NotebookId);
}
var posts = new List<Post>();
foreach (var p in db.Posts)
{
if (p.Published && followedIds.Contains(p.NotebookId))
posts.Add(p);
}
posts.Sort((a, b) => b.CreatedAt.CompareTo(a.CreatedAt));
return posts.Take(30).ToList();
The same thing, in LINQ:
var followedIds = await db.Follows
.Where(f => f.UserId == userId)
.Select(f => f.NotebookId)
.ToListAsync(ct);
return await db.Posts
.Where(p => p.Published && followedIds.Contains(p.NotebookId))
.OrderByDescending(p => p.CreatedAt)
.Take(30)
.ToListAsync(ct);
Half the code, twice as readable, and — because EF translates it to SQL — about a hundred times faster on real data.
Three rules I've come to live by
- Project early, materialize late. Use
.Select(…)to slim down what comes back from the database. Don't.ToListAsync()until you need to. AnyoverCount() > 0.Anyshort-circuits;Countdoes not.- Avoid
.Include()if you only need one column. A projection into an anonymous type is almost always cheaper.
// Don't:
var nb = await db.Notebooks
.Include(n => n.Owner)
.FirstOrDefaultAsync(n => n.Id == id);
var name = nb?.Owner.DisplayName;
// Do:
var name = await db.Notebooks
.Where(n => n.Id == id)
.Select(n => n.Owner.DisplayName)
.FirstOrDefaultAsync();
The second version translates to a single small SELECT. The first one drags an entire Notebook row and an entire User row across the wire just to read one string.
A closing thought
LINQ is, fundamentally, a way to say what you want without having to spell out how. That's a rare and lovely thing in a working programmer's life. Most code is how. LINQ gives you a small, well-lit room to work in what.