LINQ and async/await
Query collections and databases with one syntax, then do I/O without blocking the thread that is serving other requests.
LINQ
LINQ is a set of extension methods over IEnumerable<T>, and over IQueryable<T> when a provider such as EF Core can translate it. The same operators therefore work on an array, a list or a database table.
using System.Linq;
var people = new[]
{
new { Name = "Ada", Age = 36, City = "London" },
new { Name = "Grace", Age = 45, City = "New York" },
new { Name = "Alan", Age = 41, City = "London" },
};
var byCity = people
.Where(p => p.City == "London")
.OrderByDescending(p => p.Age)
.Select(p => p.Name)
.ToList();
var grouped = people
.GroupBy(p => p.City)
.Select(g => new { City = g.Key, Count = g.Count(), Oldest = g.Max(p => p.Age) });
var average = people.Average(p => p.Age);
// query syntax expresses the same operators in a different shape
var query = from p in people
where p.Age > 40
orderby p.Name
select p.Name;
// one pass, then repeated lookups that do not re-run the query
var byCityLookup = people.ToLookup(p => p.City);| Operator | Does | Deferred? |
|---|---|---|
Where | Filters | Yes |
Select | Projects every element | Yes |
OrderBy / ThenBy | Sorts, stably | Yes |
GroupBy | Buckets elements by key | Yes |
Join | Matches two sequences on a key | Yes |
Take / Skip | Pages through a sequence | Yes |
ToList / ToArray / Count | Forces the query to run | No — it executes now |
Any / First / Single | Tests or takes an element | No |
Deferred execution
Most operators build a description rather than a result. The query runs when it is enumerated, so it sees whatever the source contains at that moment, and it runs again on each enumeration.
var numbers = new List<int> { 1, 2, 3 };
var even = numbers.Where(n => n % 2 == 0); // nothing has run yet
numbers.Add(4);
var list = even.ToList(); // runs here: 2 and 4
var query = numbers.Where(n => n > 1);
Console.WriteLine(query.Count()); // executes
Console.WriteLine(query.Count()); // executes again
var snapshot = query.ToList(); // materialise when the source is volatile⚠️
Do not enumerate the same
IQueryable twice in one request: each enumeration is another database round trip, and calling a deferred query inside a loop is where N+1 queries come from. Materialise once, or shape the query so the database returns everything in a single result.async and await
public async Task<Report> BuildAsync(int id, CancellationToken ct)
{
var order = await _orders.GetAsync(id, ct); // frees the thread while waiting
var customer = await _customers.GetAsync(order.CustomerId, ct);
return new Report(order, customer);
}
// start independent operations first, then wait for all of them
var tasks = ids.Select(id => _orders.GetAsync(id, ct));
var orders = await Task.WhenAll(tasks);
// an async stream for results that arrive over time
public async IAsyncEnumerable<Order> StreamAsync(
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var order in _source.ReadAllAsync(ct))
{
if (order.Total > 0) yield return order;
}
}asyncmethods returnTask,Task<T>orValueTask<T>;async voidis only for event handlers.awaitreleases the thread while I/O is pending. It does not create a thread, and it does not make CPU work faster.- Pass a
CancellationTokenthrough every layer; a timeout that cannot cancel the work underneath it is a resource leak. - Blocking on async code with
.Resultor.Wait()can deadlock. Make the whole call chain async instead. - Await inside a loop only when each step depends on the previous one; otherwise start the tasks and await them together.
FAQ
Should every method be async?
Only methods that do I/O or call other async work. Pure computation should stay synchronous: wrapping it in
Task.Run moves the work to another thread without making it cheaper, and it adds scheduling overhead.IEnumerable or IQueryable?
Declare
IEnumerable<T> for in-memory collections and in method signatures. Keep IQueryable<T> at the data layer so a provider can translate the expression tree into SQL, and be aware that only operators the provider understands can be translated.Related
Classes and interfaces Syntax and types
Last refreshed 2026-09-18.