LINQ in C#
Query and transform data with LINQ — method syntax, query syntax, Where, Select, OrderBy, GroupBy, Join, Aggregate, deferred vs immediate execution, and custom extension operators.
LINQ Introduction
LINQ (Language Integrated Query) lets you query any collection — arrays, lists, XML, databases — using the same syntax, directly in C#. It operates on anything that implements `IEnumerable<T>`. There are two equivalent styles: method syntax (chained extension methods) and query syntax (SQL-like keywords). Most developers prefer method syntax; both compile to the same IL.
1. Method Syntax vs Query Syntax
Both syntaxes produce identical results. Method syntax uses chained extension method calls; query syntax uses `from`, `where`, `select` keywords.
2. Deferred vs Immediate Execution
LINQ queries are lazy — they describe what to do, not when. The query runs only when you iterate it or call a terminal operator like `ToList()`, `Count()`, or `First()`.
Core LINQ Operators
The core LINQ operators cover the operations you will use in nearly every query. `Where` filters, `Select` transforms, `OrderBy`/`ThenBy` sort, and element operators (`First`, `Last`, `Single`, `ElementAt`) pick specific items. Each returns `IEnumerable<T>` (except element operators which return `T`).
1. Filtering & Projecting
`Where` filters elements by a predicate. `Select` projects each element to a new shape. `SelectMany` flattens nested sequences.
2. Sorting
`OrderBy` / `OrderByDescending` for primary sort. `ThenBy` / `ThenByDescending` for secondary sort. `Reverse` flips the order.
3. Element Operators
Pick a single element from a sequence. `First`/`Last` for the first/last match, `Single` when exactly one match is expected, `ElementAt` for index access.
Grouping & Joining
Grouping and joining are among the most powerful LINQ operations. `GroupBy` partitions a sequence into groups — like SQL `GROUP BY`. `Join` combines two sequences on a matching key — like SQL `INNER JOIN`. `GroupJoin` produces a left outer join where each left element is paired with all matching right elements.
1. GroupBy
`GroupBy` partitions elements into `IGrouping<TKey, TElement>` groups. Each group has a `Key` and is itself an `IEnumerable<TElement>`.
2. Join & GroupJoin
`Join` is an inner join — only elements with matches on both sides are included. `GroupJoin` is a left outer join — every left element appears, even with no right matches.
Aggregation & Set Operations
LINQ aggregates reduce a sequence to a single value: `Count`, `Sum`, `Min`, `Max`, `Average`, and the general-purpose `Aggregate`. Set operations treat sequences as mathematical sets: `Distinct` removes duplicates, `Union` merges two sequences, `Intersect` keeps common elements, `Except` removes elements.
1. Aggregate Operators
Standard numeric aggregates plus the general `Aggregate` for custom accumulation.
2. Set Operations
`Distinct`, `Union`, `Intersect`, and `Except` treat sequences as mathematical sets — duplicates are automatically handled.
Partitioning, Quantifiers & Conversion
Partitioning operators slice a sequence: `Skip`/`Take` for pagination, `SkipWhile`/`TakeWhile` for condition-based slicing. Quantifiers check the entire sequence and return a bool: `Any`, `All`, `Contains`. Conversion operators materialise lazy sequences into concrete collections.
1. Skip, Take & Pagination
`Skip(n)` discards the first n elements. `Take(n)` keeps only the first n. Combined, they implement pagination.
2. Quantifiers & Conversion
`Any`, `All`, `Contains` answer yes/no questions. Conversion operators like `ToList`, `ToArray`, `ToDictionary`, `ToLookup` materialise results.
Generation & Combining
Not all LINQ starts from an existing collection. The `Enumerable` class provides static methods to generate sequences on the fly. Combining operators let you stitch sequences together in different ways — end-to-end, element-by-element, or by adding single items.
1. Enumerable.Range, Repeat & Empty
`Range` generates a sequence of consecutive integers. `Repeat` repeats a value n times. `Empty` produces a typed empty sequence — useful as a safe starting point.
2. Concat, Append & Prepend
`Concat` joins two sequences end-to-end. `Append` adds one element to the end. `Prepend` adds one element to the front. All return a new sequence — the originals are unchanged.
3. Zip
`Zip` pairs elements from two (or three) sequences by position. It stops at the shorter sequence. Useful for combining parallel data sources.
Type Operators & Advanced Query Syntax
`OfType<T>` and `Cast<T>` deal with non-generic or mixed-type sequences — useful when working with legacy APIs, `ArrayList`, or heterogeneous object lists. Advanced query syntax keywords (`let`, `into`, `join...into`) make complex queries more readable by introducing intermediate variables and continuing clauses.
1. OfType, Cast & ToHashSet
`OfType<T>` filters a sequence to only elements of type T. `Cast<T>` casts all elements (throws on failure). `ToHashSet` materialises to a set with O(1) lookup.
2. Advanced Query Syntax — let, into, join…into
`let` introduces an intermediate variable inside a query. `into` continues a query after a `select` or `group`. `join...into` performs a group join in query syntax.