Common LINQ Methods

The LINQ methods you'll use every day. Where, OrderBy, ThenBy and Select filter, sort and reshape; Count, Sum, Average, Min, Max, Any, All and GroupBy summarize; First, FirstOrDefault, Take, Skip and Distinct pick items.

Journeyman · C# · Updated

After this lesson you can

  • Filter, sort and reshape a collection with Where, OrderBy, ThenBy and Select
  • Summarize a collection with Count, Sum, Average, Min, Max, Any, All and GroupBy
  • Pick items from a collection with First, FirstOrDefault, Take, Skip and Distinct

Key takeaways

  • Where keeps the items you want, OrderBy and ThenBy sort them, and Select turns each item into something new, such as its name.
  • Count, Sum, Average, Min, Max, Any and All end a chain with one value: Any asks about at least one item, All about every item.
  • GroupBy sorts items into groups; each group has a Key and is itself a collection you can count and total.
  • Use First when there must be a match, since it throws if there's none; use FirstOrDefault when none is a normal answer, then check for null.
  • Take(n) gives the first n items, Skip(n) the rest and Distinct() drops duplicates. Skip(10).Take(10) is page 2 of a list shown 10 at a time.

LINQ has dozens of methods, but a handful do most of the work. Every example in this lesson uses the same small café menu:

public class Product
{
    public string Name { get; }
    public string Category { get; }
    public decimal Price { get; }

    public Product(string name, string category, decimal price)
    {
        Name = name;
        Category = category;
        Price = price;
    }
}
Coffee     Drinks   3.50
Tea        Drinks   2.75
Juice      Drinks   4.00
Croissant  Bakery   3.75
Muffin     Bakery   2.50
Bagel      Bakery   2.25

Filter, sort and reshape

Method Does Like SQL's
Where(p => ...) Keeps the items the lambda is true for WHERE
OrderBy(p => ...) Sorts by what the lambda returns, smallest first ORDER BY
OrderByDescending(p => ...) Sorts largest first ORDER BY ... DESC
ThenBy(p => ...) Sorts items that tied in the sort before it the second column of ORDER BY
Select(p => ...) Turns each item into what the lambda returns SELECT
List<Product> menu = new List<Product>
{
    new Product("Coffee", "Drinks", 3.50m),
    new Product("Tea", "Drinks", 2.75m),
    new Product("Juice", "Drinks", 4.00m),
    new Product("Croissant", "Bakery", 3.75m),
    new Product("Muffin", "Bakery", 2.50m),
    new Product("Bagel", "Bakery", 2.25m)
};

List<string> cheapest = menu
    .Where(p => p.Price < 3.00m)
    .OrderBy(p => p.Price)
    .Select(p => quot;{p.Name} {p.Price:C}")
    .ToList();

foreach (string line in cheapest)
{
    Console.WriteLine(line);
}

Console.WriteLine();

foreach (Product p in menu.OrderBy(p => p.Category).ThenByDescending(p => p.Price))
{
    Console.WriteLine(quot;{p.Category}: {p.Name}");
}

public class Product
{
    public string Name { get; }
    public string Category { get; }
    public decimal Price { get; }

    public Product(string name, string category, decimal price)
    {
        Name = name;
        Category = category;
        Price = price;
    }
}
Bagel $2.25
Muffin $2.50
Tea $2.75

Bakery: Croissant
Bakery: Muffin
Bakery: Bagel
Drinks: Juice
Drinks: Coffee
Drinks: Tea

Select can turn each product into anything: here, a line of text. Select(p => p.Name) would give just the names, and Select(p => p.Price * 1.1m) the prices with 10% added.

Summarize

These end a chain with one value:

Method Returns
Count(), Count(p => ...) How many items, or how many the lambda is true for
Sum(p => ...), Average(p => ...) The total, or the average, of what the lambda returns
Min(p => ...), Max(p => ...) The smallest, or the largest, of what the lambda returns
Any(p => ...) true if the lambda is true for at least one item
All(p => ...) true if the lambda is true for every item
List<Product> menu = new List<Product>
{
    new Product("Coffee", "Drinks", 3.50m),
    new Product("Tea", "Drinks", 2.75m),
    new Product("Juice", "Drinks", 4.00m),
    new Product("Croissant", "Bakery", 3.75m),
    new Product("Muffin", "Bakery", 2.50m),
    new Product("Bagel", "Bakery", 2.25m)
};

Console.WriteLine(menu.Count(p => p.Category == "Drinks"));
Console.WriteLine(menu.Sum(p => p.Price));
Console.WriteLine(menu.Average(p => p.Price));
Console.WriteLine(menu.Max(p => p.Price));
Console.WriteLine(menu.Any(p => p.Price > 5.00m));
Console.WriteLine(menu.All(p => p.Price > 2.00m));

public class Product
{
    public string Name { get; }
    public string Category { get; }
    public decimal Price { get; }

    public Product(string name, string category, decimal price)
    {
        Name = name;
        Category = category;
        Price = price;
    }
}
3
18.75
3.125
4.00
False
True

GroupBy

GroupBy sorts the items into groups by what the lambda returns, like SQL's GROUP BY. Each group has a Key (what its items share) and is itself a collection, so you can count and total it:

List<Product> menu = new List<Product>
{
    new Product("Coffee", "Drinks", 3.50m),
    new Product("Tea", "Drinks", 2.75m),
    new Product("Juice", "Drinks", 4.00m),
    new Product("Croissant", "Bakery", 3.75m),
    new Product("Muffin", "Bakery", 2.50m),
    new Product("Bagel", "Bakery", 2.25m)
};

foreach (var group in menu.GroupBy(p => p.Category))
{
    Console.WriteLine(quot;{group.Key}: {group.Count()} items, from {group.Min(p => p.Price):C}");
}

public class Product
{
    public string Name { get; }
    public string Category { get; }
    public decimal Price { get; }

    public Product(string name, string category, decimal price)
    {
        Name = name;
        Category = category;
        Price = price;
    }
}
Drinks: 3 items, from $2.75
Bakery: 3 items, from $2.25

(A group's full type is a mouthful, IGrouping<string, Product>, so var is the usual choice here. The groups come out in the order their first item appears.)

Pick items

Method Returns
First(p => ...) The first item the lambda is true for. Throws an InvalidOperationException if there's none.
FirstOrDefault(p => ...) The same, but null (or 0, for numbers) if there's none
Take(n), Skip(n) The first n items, or everything after the first n
Distinct() The items with duplicates removed
List<Product> menu = new List<Product>
{
    new Product("Coffee", "Drinks", 3.50m),
    new Product("Tea", "Drinks", 2.75m),
    new Product("Juice", "Drinks", 4.00m),
    new Product("Croissant", "Bakery", 3.75m),
    new Product("Muffin", "Bakery", 2.50m),
    new Product("Bagel", "Bakery", 2.25m)
};

Console.WriteLine(menu.First(p => p.Category == "Bakery").Name);

Product? soup = menu.FirstOrDefault(p => p.Name == "Soup");
Console.WriteLine(soup == null ? "No soup today" : soup.Name);

List<string> topTwo = menu.OrderByDescending(p => p.Price).Take(2).Select(p => p.Name).ToList();
Console.WriteLine(string.Join(", ", topTwo));

List<string> categories = menu.Select(p => p.Category).Distinct().ToList();
Console.WriteLine(string.Join(", ", categories));

public class Product
{
    public string Name { get; }
    public string Category { get; }
    public decimal Price { get; }

    public Product(string name, string category, decimal price)
    {
        Name = name;
        Category = category;
        Price = price;
    }
}
Croissant
No soup today
Juice, Croissant
Drinks, Bakery

Use First when there must be a match (if there isn't, something's wrong, and the exception says so), and FirstOrDefault when "none" is a normal answer, then check for null. Skip and Take together give pages: Skip(10).Take(10) is page 2 of a list shown 10 at a time.

Check yourself

3 questions to try on the lesson page.

So what

Filter with Where, sort with OrderBy, reshape with Select, summarize with Count, Sum and GroupBy, and pick with First and Take. Those few methods replace most of the loops you'd otherwise write.

Key terms

Cheat sheets