CodeGym /Courses /C# SELF /Lambda expressions in collections and

Lambda expressions in collections and LINQ

C# SELF
Level 49 , Lesson 2
Available

1. Introduction

In real projects almost every other developer spends a good chunk of their life working with collections: filtering, counting, searching, re-sorting, putting in, taking out — basically treating them like a fridge at night. You often need to extract, process and aggregate data — whether it's lists of users, products in a catalog, lines of text or any other arrays.

Almost all modern collections in .NET support functional methods — like Where, Select, Find, Any, All and others. Their power is in versatility and concise style: you just pass a "piece of logic" as a lambda expression, and the collection comes alive like you installed a new engine.

LINQ (Language Integrated Query) is not just syntactic sugar, it's a mini-language inside C# that lets you write data queries like you're using SQL or Excel. Only better: right in code, with autocompletion, types and debugger.

But all that magic works thanks to delegates — and writing a separate method every time just to filter an array is pretty tiring. That's where lambda expressions come in as inline mini-functions, turning clumsy code into elegant and expressive code.

2. Lambda expressions in standard collection methods

Lambda expressions shine in standard collection methods based on delegates, such as Find, Exists, ForEach and many others.

Example: Finding by condition

Suppose you have a list of products:

using System;
using System.Collections.Generic;

// Our product class
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

var products = new List<Product>
{
    new Product { Name = "Coffee", Price = 100 },
    new Product { Name = "Tea", Price = 70 },
    new Product { Name = "Milk", Price = 80 }
};

// Let's find the first expensive product (>90)
Product expensive = products.Find(p => p.Price > 90); // Using a lambda!
Console.WriteLine(expensive?.Name); // => Coffee

Without a lambda you'd have to write a separate method or an old-style anonymous function. This way it's one line and reads like English: "Find the product where price is greater than 90".

Example: Checking product existence

bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True (because "Tea" is cheaper than 75)

Example: Processing all items (ForEach)

Sometimes you need to do something with every element:

products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} euros"));

On analogies

In short: lambda expressions in collections are like a "make it pretty" button in a photo editor. Press it — and you get the result!

3. Lambda expressions and LINQ: magic for collections

LINQ is not just convenience, it's also a great intro to the functional style. Most LINQ methods expect delegates — which makes them perfect partners with lambda expressions.

Filtering with Where

Let's use the products list again. Now let's select only the "cheap" items:

using System.Linq;

var cheapProducts = products.Where(p => p.Price < 90);

foreach (var p in cheapProducts)
    Console.WriteLine(p.Name); // Tea, Milk

You get a new collection without writing any manual loop. Where takes a lambda-predicate (a function returning true/false) and applies it to every element.

Sorting with OrderBy

For those who like order:

var sorted = products.OrderBy(p => p.Price);

foreach (var p in sorted)
    Console.WriteLine($"{p.Name}: {p.Price}");
// Tea: 70
// Milk: 80
// Coffee: 100

Mapping (Select) – projecting data

Sometimes you don't need the whole object, just a part, e.g. a list of product names:

var names = products.Select(p => p.Name);

foreach (var name in names)
    Console.WriteLine(name); // Coffee, Tea, Milk

LINQ chains

LINQ is great because you can chain calls one after another:

var namesOfCheap = products
    .Where(p => p.Price < 90)
    .OrderBy(p => p.Name)
    .Select(p => p.Name.ToUpper());

foreach (var name in namesOfCheap)
    Console.WriteLine(name); // MOLOKO, CHAI

Looks like an assembly line: each method is a new processing stage.

Question: Why are lambda expressions better than regular methods for LINQ?

First, lambdas can be written right where they are needed. Second, lambda expressions are short and readable. Third, it's the modern C# standard — everyone writes like this, and those who don't usually don't pass interviews.

4. Practical example

During the course we built a demo app to work with a small catalog of products, users or orders. Let's add modern collection processing methods to it.

Find a user by name

public class User
{
    public string Username { get; set; }
    public int Age { get; set; }
}

var users = new List<User>
{
    new User{ Username = "Alice", Age = 21 },
    new User{ Username = "Bob", Age = 26 },
    new User{ Username = "Charlie", Age = 32 }
};

// Find user by name
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26

Filter by age

var adults = users.Where(u => u.Age >= 18);

foreach (var u in adults)
    Console.WriteLine(u.Username); // Alice, Bob, Charlie

Count users

int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob and Charlie)

Check if all users are adults

bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True

Is there at least one minor?

bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False

5. LINQ: How it works under the hood

When you write, for example, Where(u => u.Age > 20), it's basically the same as creating a loop that iterates over all elements and checks the condition for each. LINQ just does this invisibly and nicely, wrapping your predicate into a delegate.

Without lambda expressions you'd have to construct things like:

public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);

Or old-school anonymous methods:

var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });

All of that is bulky and dull. With a lambda — elegant and modern.

6. Delegates and standard types: Func, Action, Predicate

Not only LINQ loves lambda expressions. Many standard collection methods accept specialized delegates, for example:

  • Predicate<T> — for methods Find, Exists, RemoveAll
  • Func<T, TResult> — for LINQ methods, projections, computations
  • Action<T> — for methods that do something with an element but return nothing (ForEach)

Here's how it looks in practice:

// Predicate<T>
users.RemoveAll(u => u.Age < 30); // Removed everyone younger than 30

// Func<T, TResult>
var names = users.Select(u => u.Username);

// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));

7. Cheat sheet of collection methods with lambda expressions

Method What it does Delegate type Lambda example
Where
Filters elements
Func<T, bool>
p => p.Price > 100
Select
Projects, transforms
Func<T, U>
p => p.Name
OrderBy
Sorts by a key
Func<T, K>
u => u.Age
FirstOrDefault
First element matching condition
Func<T, bool>
u => u.Username == "Bob"
Any
Is there at least one element matching condition
Func<T, bool>
u => u.Age < 18
All
Do all elements satisfy the condition
Func<T, bool>
u => u.Age >= 18
Count
Number of elements matching condition
Func<T, bool>
p => p.Price > 50
ForEach
Do something for each element
Action<T>
u => Console.WriteLine(u.Name)
RemoveAll
Removes all by predicate
Predicate<T>
u => u.Age < 18

8. Common mistakes and gotchas

One of the most common mistakes is forgetting that LINQ doesn't modify the original collection, it returns a new sequence. So after code like var sorted = users.OrderBy(u => u.Age); the users collection will remain in its original order! That can be confusing: sometimes it looks like everything is sorted — but actually it's not.

Another nuance: methods like Where, Select and others return objects of type IEnumerable<T>. This is a "lazy" collection — real processing starts when you actually enumerate it (foreach, ToList(), etc.). So if you want to materialize the result, don't forget to call ToList() or ToArray():

var sortedList = users.OrderBy(u => u.Age).ToList();

Also remember: if a lambda expression captures variables from outside its scope (closures), those variables continue to "live" in memory as long as the lambda reference is alive. Not a big deal, but if you use a lambda inside a long-lived object and it captured a "huge array", that array will hang in memory together with the lambda.

And one more: use meaningful parameter and variable names — it greatly improves readability, especially if you have several levels of nested lambdas.

2
Task
C# SELF, level 49, lesson 2
Locked
Filtering a list using lambda expressions
Filtering a list using lambda expressions
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION