Exploring Advanced LINQ Operations in C#

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

  • Range and Repeat generate sequences.
  • Any and All evaluate conditions.
  • Concat merges sequences; Union merges and deduplicates.
  • GroupBy organizes data into groups.
  • DefaultIfEmpty substitutes default values for empty results.
  • SequenceEqual compares sequences element-wise.

LINQ also includes sorting (OrderBy), summation (Sum), counting (Count), and reversal (Reverse) methods.

Tags: C# LINQ Query Collections Data Transformation

Posted on Sun, 10 May 2026 14:07:00 +0000 by moboter