CodeGym /Courses /C# SELF /Delegates as a type for lambda expressions

Delegates as a type for lambda expressions

C# SELF
Level 50, Lesson 0
Available

1. Introduction

Imagine you have a coffee machine. Usually it just brews coffee and doesn't bother anyone, but sometimes the boss says: "Can you, please, after brewing the coffee shout 'Done!'?" — and here you need a configuration. We don't rewrite the coffee machine itself. We just create a function that it will execute at the end.

In C# this role is played by delegates — they let you pass chunks of code (methods, lambdas or anonymous methods) into methods so that the method can call them at the right moment. Roughly speaking, a delegate is a type that can hold references to methods with a specific signature.

Delegate definition

In C# a delegate is defined with the delegate keyword. Example:

// Delegate that refers to methods taking an int and returning a bool
public delegate bool PredicateInt(int x);

Now a variable of type PredicateInt can refer to any method (or lambda!) that takes one int and returns a bool.

What are delegates for?

  • Passing logic as an argument (for example, for sorting, filtering, handling events)
  • Subscribing to events (we'll talk about that later)
  • Implementing callbacks
  • Flexible APIs where part of the behavior is defined by the caller

Simple visual scheme

Delegate type Signature Example invocation
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. How a lambda turns into a delegate

Syntax

When you write a lambda expression, for example x => x > 5, you are basically creating a delegate object. A lambda doesn't "live" in a vacuum: it needs a type (someone must know its parameter list and return type). That's why a lambda expression in C# is always implicitly (or explicitly) converted into a delegate.

Example 1: Binding a delegate to a method

// Explicitly define a delegate
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // Assign a lambda to a variable of type MyPredicate
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

Example 2: Using standard delegates

C# includes a set of standard generic delegates: Action, Func<>, Predicate<>. They are used almost everywhere you write lambdas in code.

// Use Func<int, bool>
Func<int, bool> isPositive = number => number > 0;
Console.WriteLine(isPositive(-5)); // false

3. Standard delegates: Func, Action, Predicate

Func<...>

Used for methods that take something and return something.

Signature:
— The last type is the return value, the ones before it are parameter types, for example:
Func<int, string> — takes an int, returns a string

Func<int, string> intToString = number => "Number: " + number;
Console.WriteLine(intToString(7)); // "Number: 7"

Action<...>

Used when you need to do something but don't need to return anything (void).

Action<string> printHello = name => Console.WriteLine("Hi, " + name + "!");
printHello("John"); // "Hi, John!"

Predicate<T>

Basically a shorthand for Func<T, bool>. Used when you need a boolean check on an object (true/false).

Predicate<int> isOdd = x => x % 2 != 0;
Console.WriteLine(isOdd(3)); // true

Visual cheat-sheet

Delegate Signature Use
Func<T1, TResult>
Result(T1)
Transformation, projection
Action<T1>
void(T1)
Side-effects, output
Predicate<T>
bool(T)
Filtering, searching

Which delegate type to choose for a lambda?

  • If a return value is expected, pick Func<...>
  • If the method returns nothing (void), use Action<...>
  • If you need a condition check, use Predicate<T>

Example: Filtering a list

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

// Expects Predicate<int>
List<int> evenNumbers = numbers.FindAll(x => x % 2 == 0);
Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6

4. Useful nuances

Lambda expressions and collection methods: what's under the hood?

When you call a collection method passing a lambda, for example:

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

The Where method expects an argument of type Func<T, bool>. So your lambda u => u.Age >= 18 is turned by the compiler into a delegate object of that type.

Flowchart: how it works


Your lambda           -->      C# compiler        -->     Delegate object (Func<T, bool>)
(u => u.Age >= 18)          [type known]                (Ready to be invoked in Where())

More on typing: type inference

Usually the delegate type is inferred by the compiler automatically if it's clear from context. For example, for the method List<T>.Find a Predicate<T> is expected, and the compiler knows the parameter type from the method signature.

List<string> words = new List<string> { "one", "two", "three" };
var result = words.Find(word => word.Length == 5); // Find expects Predicate<string>
Console.WriteLine(result); // "three"

If the context is not clear, you'll need to help the compiler:

// Explicitly specify the type
Func<int, bool> check = x => x > 10;

Returning delegates: function factories

Sometimes methods can return delegates — i.e., create "function factories". This is handy for generating dynamic behavior.

// Function returning a delegate (a lambda)
Func<int, int> GetMultiplier(int factor)
{
    return x => x * factor;
}

var times3 = GetMultiplier(3);
Console.WriteLine(times3(5)); // 15

This works because the lambda (x => x * factor) captures the variable factor from the outer context (closure) and is returned as an object of type Func<int, int>.

5. Errors and confusions with delegates and lambdas

Signature mismatch

The compiler won't let you assign a lambda to a delegate if the parameters or return type don't match.

Func<int, int> f = x => "You can't return a string!"; // Compilation error

Error trying to use a lambda without a delegate

You can't just write a lambda and try to invoke it without a type:

// This won't work - the compiler can't infer the type
// var myFunc = x => x * 2; // Error CS0815 
// myFunc(10);

To make it work, you need to explicitly specify the type or provide a context:

Func<int, int> myFunc = x => x * 2;
Console.WriteLine(myFunc(10)); // 20

Confusion between Action, Func and Predicate

Sometimes you can accidentally pick the wrong delegate type, which causes a signature mismatch. Remember the simple rule: Func — when there's a result, Action — when there's no result (void), Predicate<T> — when you need a boolean answer (bool).

2
Task
C# SELF, level 50, lesson 0
Locked
Creating and Using a Delegate for a Lambda Expression
Creating and Using a Delegate for a Lambda Expression
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION