CodeGym /Courses /C# SELF /Lambda expressions with Fu...

Lambda expressions with Func, Action, Predicate

C# SELF
Level 50, Lesson 1
Available

1. Introduction

You know what's interesting? A lambda expression by itself is like a recipe without a pot. It describes what to do, but it needs a "container" to live in the code. In C# there are ready-made generic delegate types for that: Func, Action and Predicate.

Think of them as ready-made molds for your lambdas — pick the right one and pour your logic in. No custom reinvented delegates when you've already got what you need.

Bit of history

At first, when you needed to pass a function as a parameter, you had to declare your own delegate type. That was long, annoying and looked like this:

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

When Func, Action and Predicate landed in C#, you could forget about manual delegate declarations in 90% of cases. Now it looks way simpler and more universal:

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — a function with a return

Syntax and purpose

Func is a generic delegate that takes from 0 to 16 parameters and returns a value.

Func<int, int, int> sum = (x, y) => x + y;

Func<type1, type2, ..., typeN, TResult> — all type parameters up to the last one are arguments, the last one is the return type.

Examples

1. Sum of two numbers

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. Square a number

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. No parameters

Func<string> greet = () => "Hello, lambda!";
Console.WriteLine(greet());

Visual scheme

Signature Example Description
Func<int, int>
x => x * 2
Takes a int, returns an int
Func<int, int, int>
(a, b) => a + b
Two int, returns an int
Func<string>
() => "hi"
Takes nothing, returns a string

How it looks in your app

Say in our mini-app (a mock "User directory") we have a list of numbers — we want to apply different processing to it. For example, square them or add with a fixed number:

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — an action without a return

Action is a generic delegate for methods that do something (for example, print to the screen) but don't return anything.

It can take from 0 to 16 parameters, but always returns void.

Examples

1. Print to the screen

Action<string> print = text => Console.WriteLine("Data: " + text);
print("Hello, world!");

2. Action with no parameters

Action greet = () => Console.WriteLine("Welcome'!");
greet();

3. Action with multiple parameters

Action<int, int> showSum = (a, b) => Console.WriteLine($"Sum: {a + b}");
showSum(2, 3); // Sum: 5

Visual scheme

Signature Example Description
Action
() => ...
No parameters, no return
Action<int>
x => ...
One parameter
Action<int, string>
(x, s) => ...
Multiple parameters

In our app

Add a method to the user directory to print all names:

List<string> names = new() { "Anna", "Boris", "Vika" };
Action<string> printName = name => Console.WriteLine("User': " + name);

names.ForEach(printName);
// or like this: names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — yes or no?

When you need a function that returns only true or false for a single parameter, use Predicate<T>. It's just a delegate that takes one parameter and returns a bool.

Predicate is the official "we need a boolean check" wrapper for a lambda.

Examples

1. Check if a number is greater than 5

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. Use with List<T>.Find

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // uses Predicate<int>
Console.WriteLine(found); // 7

3. Are they all adults?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll accepts Predicate<int>

What's the difference from Func<T, bool>?

Practically, they're fully interchangeable. Even Microsoft docs say: "Predicate<T> is just Func<T, bool> for specific APIs". But some standard library methods ask specifically for Predicate.

5. How lambdas "fit" into Func, Action, Predicate

When you write a lambda, C# analyzes: "Oh, its shape matches the required delegate — we can substitute it!"

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

Lambdas everywhere! But under the hood — three different delegates with different signatures.

Practical example in "real" code

List<User> users = new() {
    new User("Anna", 24),
    new User("Boris", 17),
    new User("Vika", 31),
};

// Function returning only adult users (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("List of adults: " + string.Join(", ", adults.Select(u => u.Name)));

And if we want to print all user names with an Action<User>:

users.ForEach(user => Console.WriteLine(user.Name));

Get their names (Func<User, string>):

IEnumerable<string> names = users.Select(user => user.Name);

Table for clarity

Delegate Signature Lambda example Where used
Func<T, U>
T → U
user => user.Name
Select, any transformations
Action<T>
T → void
user => Console.WriteLine(...)
ForEach, action methods
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, filters

6. Examples with the internal app: step by step

Let's extend our small "User directory" app. Suppose we have this User class:

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — check if a user is an adult

Func<User, bool> isAdult = user => user.Age >= 18;

Use in LINQ:

var adults = users.Where(isAdult);

2. Predicate<User> — find an active user

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — deactivate a user

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — get a short description

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

All these lambdas are perfectly good "code-as-data" that you can pass to methods, store in variables, and compose.

7. Non-obvious nuances and common mistakes

1. A lambda must match the delegate signature. If the signature doesn't match, you'll get a compile error.

Func<int, string> wrong = x => x * 2; // error: string expected, got int
// Correct:
Func<int, string> right = x => (x * 2).ToString();

2. Remember void vs return. Action doesn't return a value — try assigning something like Action<int> a = x => x * x; and it won't work because a value is returned though it shouldn't.

3. Predicate<T> and Func<T, bool> are often interchangeable, but not always. Sometimes collection methods expect specifically Predicate<T>, sometimes Func<T, bool>. Direct assignment doesn't work without explicit wrapping.

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // error
// But:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // can convert via constructor
2
Task
C# SELF, level 50, lesson 1
Locked
Using `Action` to process a list of strings
Using `Action` to process a list of strings
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION