C# LINQ Cheat Sheet
Covers LINQ query and method syntax in C#, common operators like Where, Select and GroupBy, joins, and deferred versus immediate execution.
Query vs Method Syntax
Two equivalent ways to write a LINQ query.
using System.Linq;int[] numbers = { 5, 2, 8, 1, 9, 3 };// Method syntaxvar evens = numbers.Where(n => n % 2 == 0).OrderBy(n => n).ToList();// Query syntax (equivalent)var evens2 = from n in numbers where n % 2 == 0 orderby n select n;
Common Operators
Filtering, projecting, and aggregating sequences.
var names = new List<string> { "Alice", "Bob", "Charlie" };var upper = names.Select(n => n.ToUpper()); // Projectionvar withA = names.Where(n => n.Contains('a')); // Filterbool any = names.Any(n => n.StartsWith("B")); // True if any matchbool all = names.All(n => n.Length > 2); // True if all matchint count = names.Count(n => n.Length == 5);string? first = names.FirstOrDefault(n => n.StartsWith("Z")); // null if none foundint sum = numbers.Sum();int max = numbers.Max();var grouped = names.GroupBy(n => n[0]); // Group by first letter
Joins & Aggregation
Combining and summarizing data from multiple sequences.
var joined = from o in orders join c in customers on o.CustomerId equals c.Id select new { c.Name, o.Total };decimal totalRevenue = orders.Sum(o => o.Total);var totalsByCustomer = orders .GroupBy(o => o.CustomerId) .Select(g => new { CustomerId = g.Key, Total = g.Sum(o => o.Total) });
Deferred vs Immediate Execution
When a LINQ query actually runs.
- Deferred execution- Where, Select, OrderBy build a query but don't run until it's enumerated (foreach, ToList())
- Immediate execution- ToList(), ToArray(), Count(), Sum(), First() execute the query right away
- IEnumerable<T> vs IQueryable<T>- IQueryable builds an expression tree translated to SQL by providers like EF Core
- Multiple enumeration- Re-enumerating a deferred query re-runs it; call ToList() once to cache the results
- Select vs SelectMany- Select projects 1:1; SelectMany flattens a collection of collections into one sequence
- Null-safety- FirstOrDefault/SingleOrDefault return default(T) instead of throwing when nothing matches
Writing Custom LINQ Operators
Extending IEnumerable<T> with your own composable, deferred-execution operator.
public static class EnumerableExtensions{ public static IEnumerable<T> DistinctBy<T, TKey>( this IEnumerable<T> source, Func<T, TKey> keySelector) { var seen = new HashSet<TKey>(); foreach (var item in source) { if (seen.Add(keySelector(item))) yield return item; // yield return preserves deferred execution } }}var uniqueByCity = people.DistinctBy(p => p.City);
Expression Trees & IQueryable Internals
How Where/Select build an expression tree instead of executing code for IQueryable sources.
Expression<Func<Person, bool>> predicate = p => p.Age > 18;// predicate.Body is an AST node (BinaryExpression), not compiled ILvar param = predicate.Parameters[0];Console.WriteLine(predicate.Body); // "(p.Age > 18)"// EF Core's IQueryable<T> provider walks this tree and translates it// to SQL instead of invoking a compiled delegate:var adults = dbContext.People.Where(predicate).ToList();// Compiling manually when needed:Func<Person, bool> compiled = predicate.Compile();
Closures, Captured Variables & Chained Deferral
Classic bugs from combining deferred execution with mutable captured state.
var queries = new List<Func<int>>();foreach (var n in new[] { 1, 2, 3 }){ queries.Add(() => n * n); // C# 5+: each iteration captures its own 'n'}int threshold = 10;var filtered = numbers.Where(x => x > threshold);threshold = 100;var results = filtered.ToList(); // uses 100, NOT 10 — the lambda captures // the variable, not its value at Where() time// Fix: snapshot the value before building the queryint snapshot = threshold;var safeFiltered = numbers.Where(x => x > snapshot);
PLINQ and Async Streaming
Parallelizing CPU-bound LINQ pipelines and streaming async results.
var squares = numbers .AsParallel() .WithDegreeOfParallelism(4) .Where(n => IsExpensiveCheck(n)) .Select(n => n * n) .ToList(); // PLINQ: CPU-bound, order not guaranteed// Async streaming with IAsyncEnumerable<T> (C# 8+, not classic LINQ-to-Objects)await foreach (var item in GetItemsAsync()){ if (item.IsValid) yield return item;}var top5 = await source.Where(x => x.Active).Take(5).ToListAsync(); // EF Core
Performance & Provider Considerations
Things that quietly change a LINQ query from fast to slow, or from SQL to client-side.
- Client-side evaluation- Calling a non-translatable method (e.g. a local C# function) inside an IQueryable Where forces EF Core to pull data client-side or throw
- N+1 queries- Accessing a navigation property inside a loop without Include() triggers one query per iteration
- Include vs projection- Select-ing only needed columns is faster than Include()-ing whole related entities when you don't need the full object
- AsNoTracking- Skips EF Core's change tracker for read-only queries, reducing memory and CPU overhead
- Count() vs Any()- Any() short-circuits on the first match; Count() > 0 enumerates/counts everything — prefer Any() for existence checks
- OrderBy before GroupBy- In LINQ-to-Objects, GroupBy preserves encounter order within groups, so sort first if group order matters
- ToList() placement- Materializing too early defeats provider-side filtering (e.g. SQL WHERE); materialize as late as possible
Calling .ToList() too early forces LINQ-to-Entities queries to execute in memory instead of the database — keep IQueryable chains unmaterialized until the final query is fully built.