.NET Academy

LINQ basics: filter, project, sort

Query collections declaratively with Where, Select, OrderBy and LINQ's aggregate methods.

13 min read

You will learn

  • Filter with Where and transform with Select
  • Sort with OrderBy, ThenBy and their Descending versions
  • Use First, FirstOrDefault, Any, All and Count
  • Compute Sum, Average, Min and Max

Say what you want, not how to loop

Here's a familiar task — the names of adult users, sorted — written with loops:

var result = new List<string>();
foreach (var user in users)
{
    if (user.Age >= 18)
        result.Add(user.Name);
}
result.Sort();

And with LINQ (Language-Integrated Query):

var result = users
    .Where(u => u.Age >= 18)
    .Select(u => u.Name)
    .Order()
    .ToList();

The LINQ version reads like a description of the result. Each method takes a lambda — everything you learned about delegates pays off here. LINQ works on anything that implements IEnumerable<T>: arrays, lists, dictionaries, sets, strings, and (in Track 10) database tables.

For the examples below:

record Product(string Name, string Category, decimal Price, int Stock);
 
var products = new List<Product>
{
    new("Laptop", "Electronics", 999m, 3),
    new("Mouse", "Electronics", 19.99m, 10),
    new("Notebook", "Office", 4.5m, 100),
    new("Pen", "Office", 1.2m, 0),
};

Where: filter

var cheap = products.Where(p => p.Price < 20);                 // Mouse, Notebook, Pen
var inStockOffice = products.Where(p => p.Category == "Office" && p.Stock > 0);

Select: transform each element

var names = products.Select(p => p.Name);                       // "Laptop", "Mouse", ...
var labels = products.Select(p => $"{p.Name} ({p.Price:F2})");
var withTax = products.Select(p => new { p.Name, Total = p.Price * 1.2m });   // anonymous type

Sorting

products.OrderBy(p => p.Price);                     // cheapest first
products.OrderByDescending(p => p.Stock);           // most stock first
products.OrderBy(p => p.Category).ThenBy(p => p.Name);   // tie-breaker with ThenBy
new[] { 3, 1, 2 }.Order();                          // sort simple values by themselves

Picking single elements

MethodReturnsWhen nothing matches
First(pred)the first matchthrows
FirstOrDefault(pred)the first matchdefault (null for classes)
Single(pred)the only matchthrows (also throws if several match)
Last(pred), ElementAt(i)…throws
MaxBy(sel) / MinBy(sel)element with the largest/smallest keynull for empty sequences of classes
var laptop = products.First(p => p.Name == "Laptop");
var garden = products.FirstOrDefault(p => p.Category == "Garden");   // null
var priciest = products.MaxBy(p => p.Price);                         // Laptop

Asking questions

bool anySoldOut = products.Any(p => p.Stock == 0);      // true
bool allCheap = products.All(p => p.Price < 1000);      // true
int officeCount = products.Count(p => p.Category == "Office");   // 2
bool hasPen = products.Select(p => p.Name).Contains("Pen");

Any() with no arguments checks for emptiness — clearer and often faster than Count() > 0.

Aggregates

decimal total = products.Sum(p => p.Price * p.Stock);
decimal average = products.Average(p => p.Price);
decimal cheapest = products.Min(p => p.Price);
int mostStock = products.Max(p => p.Stock);

Paging, distinct and conversion

products.Skip(10).Take(10);                  // page 2 with 10 per page
products.Select(p => p.Category).Distinct(); // "Electronics", "Office"
products.Where(...).ToList();                // materialize into a List<T>
products.ToArray();
products.ToDictionary(p => p.Name);          // key selector → Dictionary<string, Product>

Chaining

Each method returns a new sequence, so you chain them into a pipeline — formatted one call per line:

var report = products
    .Where(p => p.Stock > 0)
    .OrderByDescending(p => p.Price * p.Stock)
    .Take(3)
    .Select(p => $"{p.Name}: {p.Price * p.Stock:F2}");

LINQ never modifies the source

Where, Select, OrderBy… all return new sequences. The original list is untouched — unlike List.Sort() or RemoveAll(), which change the list in place.

Try it

Program.cs
Loading editor…

C# runtime not loaded

Key takeaways

  • LINQ queries any IEnumerable<T> with chained methods that take lambdas.
  • Where filters, Select transforms, OrderBy/ThenBy sort.
  • First/Single throw on no match; the OrDefault versions return default.
  • Any, All, Count, Sum, Average, Min, Max answer questions about a sequence.

Exercise

Using LINQ, implement CheapNames (names of products priced at or under maxPrice, sorted alphabetically), InventoryValue (sum of price × stock), MostExpensiveIn (the priciest product in a category, or null) and AllInStock (true when every product has stock > 0). Don't change Catalog — the checks use it.

Program.cs
Loading editor…

C# runtime not loaded

Hint 1

CheapNames: products.Where(p => p.Price <= maxPrice).Select(p => p.Name).Order().ToList()

Hint 2

InventoryValue: products.Sum(p => p.Price * p.Stock)

Hint 3

MostExpensiveIn: filter by category, then .OrderByDescending(p => p.Price).FirstOrDefault() — or MaxBy(p => p.Price).

Show solution
Console.WriteLine(string.Join(", ", CheapNames(Catalog(), 20)));

List<string> CheapNames(List<Product> products, decimal maxPrice) =>
    products
        .Where(p => p.Price <= maxPrice)
        .Select(p => p.Name)
        .Order()
        .ToList();

decimal InventoryValue(List<Product> products) =>
    products.Sum(p => p.Price * p.Stock);

Product? MostExpensiveIn(List<Product> products, string category) =>
    products
        .Where(p => p.Category == category)
        .OrderByDescending(p => p.Price)
        .FirstOrDefault();

bool AllInStock(List<Product> products) =>
    products.All(p => p.Stock > 0);

List<Product> Catalog() =>
[
    new("Laptop", "Electronics", 999m, 3),
    new("Mouse", "Electronics", 19.99m, 10),
    new("Notebook", "Office", 4.5m, 100),
    new("Pen", "Office", 1.2m, 0),
    new("Monitor", "Electronics", 199m, 0),
    new("Desk", "Office", 250m, 2),
];

record Product(string Name, string Category, decimal Price, int Stock);

Quiz

4 questions · 3 correct to pass
  1. 1.What does `numbers.Where(n => n > 10)` return?

  2. 2.What does `people.Select(p => p.Name)` produce?

  3. 3.What's the difference between First() and FirstOrDefault()?

  4. 4.How do you sort people by last name, then by first name?

To complete this lesson

  • ○ Solve the exercise
  • ○ Pass the quiz (3/4 correct)