LINQ (Language Integrated Query) provides a unified model for querying data across diverse sources. It integrates directly into C# without introducing a separate language.
LINQ can be categorized functionally into LINQ to Objects for in-memory collections and LINQ to Providers for custom data sources like XML or JSON. Syntactically, it offers SQL-like query expressions and method-based extensions. The extension methods are widely used due to their flexibility and support for custom functions.
All LINQ to Objects queries can be expressed using extension methods. These operations typically work on IEnumerable<T> sequences, often returning a transformed IEnumerable<TResult>. For example:
var filteredIds = users
.Where(person => person.Name.Contains("Wang"))
.Select(person => person.Id);
Intermediate variables can be introduced within queries:
int[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var aboveAverage = from val in values
let avg = values.Average()
let square = Math.Pow(val, 2)
where square > avg
select val;
The Select method can also utilize the element index:
var indexed = items
.Select((element, idx) => new { Element = element, Index = idx })
.ToList();
Element Retrieval Methods
First, Last, and Single retrieve specific elements. Their OrDefault variants return default values instead of throwing exceptions for empty sequences. Single requires exactly one matching element.
new[] { "x", "y" }.First(item => item.Equals("y")); // "y"
new[] { "x", "y" }.Single(item => item.Equals("z")); // InvalidOperationException
new[] { "x", "y" }.SingleOrDefault(item => item.Equals("z")); // null
Set Operations
Except returns elements present in the first collection but not in the second:
int[] primary = { 1, 2, 3, 4 };
int[] secondary = { 0, 2, 3, 5 };
var difference = primary.Except(secondary); // { 1, 4 }
For custom types, implement IEquatable<T>:
class Product : IEquatable<Product>
{
public string Code { get; set; }
public bool Equals(Product other) => Code == other.Code;
public override int GetHashCode() => Code?.GetHashCode() ?? 0;
}
Dimensionality Reduction
SelectMany flattens nested collections:
int[][] matrix = { new[] { 1, 2 }, new[] { 3, 4 } };
var flattened = matrix.SelectMany(arr => arr); // [1, 2, 3, 4]
It also computes Cartesian products:
var setA = new List<string> { "A1", "A2" };
var setB = new List<string> { "B1", "B2", "B3" };
var combinations = setA.SelectMany(a => setB.Select(b => a + b));
// ["A1B1", "A1B2", "A1B3", "A2B1", "A2B2", "A2B3"]
Aggregation
Aggregate performs cumulative operations:
int[] numbers = { 1, 2, 3, 4, 5 };
int total = numbers.Aggregate((acc, current) => acc + current); // 15
A seed can specify initial accumulator state:
var analysis = numbers.Aggregate(
new { Count = 0, EvenCount = 0 },
(acc, num) => new {
Count = acc.Count + 1,
EvenCount = acc.EvenCount + (num % 2 == 0 ? 1 : 0)
});
Joining Collections
LINQ supports various join operations. Given two sequences:
var left = new List<string> { "a", "b", "c" };
var right = new List<string> { "a", "c", "d" };
Inner Join:
var inner = from l in left
join r in right on l equals r
select new { Left = l, Right = r };
// { ("a","a"), ("c","c") }
Left Outer Join:
var leftOuter = from l in left
join r in right on l equals r into temp
from t in temp.DefaultIfEmpty()
select new { Left = l, Right = t };
// { ("a","a"), ("b",null), ("c","c") }
Full Outer Join combines left and right joins via Union.
Pagination
Skip and Take enable paging:
var page = data.Skip((pageNumber - 1) * pageSize).Take(pageSize);
SkipWhile and TakeWhile conditionally exclude or include elements:
int[] values = { 42, 42, 6, 6, 42 };
var skipped = values.SkipWhile(v => v == 42); // [6, 6, 42]
Pairing Elements
Zip combines corresponding elements from two sequences:
int[] nums = { 3, 5, 7 };
string[] labels = { "three", "five", "seven" };
var pairs = nums.Zip(labels, (n, l) => $"{n}={l}");
// ["3=three", "5=five", "7=seven"]
Type Filtering and Conversion
OfType filters by type, while Cast attempts conversion:
IEnumerable<object> mixed = new object[] { 1, "text", 2.5 };
var strings = mixed.OfType<string>(); // ["text"]
var numbers = mixed.Cast<int>(); // InvalidCastException
Lookup Creation
ToLookup groups elements into an indexable structure:
string[] terms = { "one", "two", "three" };
var lengthLookup = terms.ToLookup(term => term.Length);
var threeLetter = lengthLookup[3]; // ["one", "two"]
Distinct Elements
Distinct removes duplicates, requiring custom comparers for complex types:
int[] duplicates = { 1, 2, 2, 3, 1 };
var unique = duplicates.Distinct(); // [1, 2, 3]
Dictionary Conversion
ToDictionary creates dictionaries from sequences:
var people = GetPeople();
var idMap = people.ToDictionary(p => p.Id, p => p.Name);
Additional Utilities
RangeandRepeatgenerate sequences.AnyandAllevaluate conditions.Concatmerges sequences;Unionmerges and deduplicates.GroupByorganizes data into groups.DefaultIfEmptysubstitutes default values for empty results.SequenceEqualcompares sequences element-wise.
LINQ also includes sorting (OrderBy), summation (Sum), counting (Count), and reversal (Reverse) methods.