Skip to content
Lesson 9 of 12

Step 1 of 6 · Reading · ~3 min

Learn

Collections and LINQ

LINQ - Querying Collections

By the end of this lesson you can filter, transform, sort, trim and total a collection by chaining LINQ methods, and you know which ones are lazy and which throw on an empty sequence.

You already know the loop way: create an accumulator, iterate, test with if, update. LINQ (Language INtegrated Query) is the declarative alternative: you describe what you want as a pipeline of operations, and the library does the iterating. Add using System.Linq; and every array and List<T> gains a family of chainable methods.

The building blocks

using System.Linq;

int[] prices = { 30, 12, 45, 12, 8, 19 };

prices.Where(p => p < 20)          // filter:  12, 12, 8, 19
prices.Select(p => p * 2)          // map:     60, 24, 90, 24, 16, 38
prices.OrderBy(p => p)             // sort ascending:  8, 12, 12, 19, 30, 45
prices.OrderByDescending(p => p)   // sort descending: 45, 30, 19, 12, 12, 8
prices.Distinct()                  // drop repeats:    30, 12, 45, 8, 19
prices.Take(2)                     // first two:       30, 12
prices.Skip(4)                     // all but first 4: 8, 19

The p => p < 20 syntax is a lambda, a tiny inline function: "given p, return whether p is under 20". Where keeps the elements for which the lambda is true; Select replaces each element with the lambda's result.

Some methods end a pipeline by producing a single value:

prices.Sum()              // 126
prices.Max()              // 45
prices.Count(p => p > 15) // 3
prices.Any(p => p > 40)   // True
prices.First()            // 30

To get a collection back, finish with .ToList() or .ToArray(). To print a sequence on one line, use string.Join(", ", sequence).

Chaining: the power is in the order

Each method returns a sequence, so they snap together left to right, and each step receives the output of the one before:

// the two cheapest items under 20
var cheap = prices
    .Where(p => p < 20)       // 12, 12, 8, 19
    .OrderBy(p => p)          // 8, 12, 12, 19
    .Take(2);                 // 8, 12
Console.WriteLine(string.Join(" ", cheap));   // 8 12

Say the requirement out loud and translate it word by word. The same steps in another order mean something else:

prices.Take(2).OrderBy(p => p)   // 12, 30: first two items, THEN sorted
prices.OrderBy(p => p).Take(2)   // 8, 12:  sorted, THEN the first two

Laziness and empty sequences

  • Deferred execution. Where, Select, OrderBy and the rest do not run when you write them. They run when something consumes the sequence (Sum(), ToList(), a foreach). A chain costs nothing until you pull results through it.
  • Empty sequences. Sum() and Count() of an empty sequence return 0. But Max(), Min(), Average() and First() throw InvalidOperationException. When "nothing matched" is a legal outcome, guard with Any() or use FirstOrDefault().

Common mistakes

  • Steps in the wrong order. Take before the sort picks from the unsorted data.
  • Forgetting that Distinct() is a step of its own. Without it, repeated values stay repeated.
  • Calling Max() on a filtered sequence that may be empty. It throws; check Any() first.
  • Expecting OrderBy to change the original array. LINQ never modifies its source; it returns a new sequence.

Your exercise

The starter parses a line of integers into nums. Use a LINQ pipeline to print the values the exercise asks for. The most likely mistakes are leaving duplicates in the result and taking the first values before sorting.

Up nextInheritance and virtual/overrideInheritance, Interfaces, and Exceptions

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…