CodeGym /Courses /C# SELF /Lambda expressions for event and callback

Lambda expressions for event and callback

C# SELF
Level 50, Lesson 3
Available

1. Introduction

In C# (and in .NET in general) events are one of the ways objects talk to each other. For example, you have a button that should trigger something when clicked. You could implement event handling by declaring a separate method and then explicitly subscribing that method to the event... But often you just want to "describe the reaction right here" without spreading logic across different parts of the file.

Lambda expressions are perfect for this — they let you literally write the reaction to an event right where you subscribe to it. Minimal ceremony — maximum benefit.

The same applies to callbacks: sometimes you pass some logic into a method to be executed "later", for example when an operation finishes, and again — lambda expressions make this simple and clear: all the needed logic is right there at the call site.

Events and callbacks: the gist and when to use lambdas

Event is basically a "sign" on an object: "someone can subscribe here and do something when something happens."

Callback is "I'm giving you a function now, call it when you need to." Very similar to "leave a request — we'll call you back."

When you first learned about delegates and events you probably saw syntax like this:

button.Click += MyButtonClickHandler;

Where MyButtonClickHandler is a named method you have to declare somewhere. But when the logic is simple, you'd prefer a couple of lines without creating a method for a single action.

This is where lambdas step in:

button.Click += (sender, args) => { Console.WriteLine("Button clicked!"); };

Concise, clear, right here!

2. Lambdas as event handlers: from simple to interesting

Simple form: light blinked — print something to the console

Suppose we have a Button class, and we want to react to its Click event.

// Imagine a button like this:
public class Button
{
    public event EventHandler? Click;

    public void SimulateClick() // For example: programmatically "click"
    {
        // If someone subscribed — invoke handlers
        Click?.Invoke(this, EventArgs.Empty);
    }
}

Now let's use this button:

var button = new Button();

// Subscribe to the button's event using a lambda!
button.Click += (sender, args) =>
{
    Console.WriteLine("Yay! The button was clicked!");
};

button.SimulateClick(); // => Yay! The button was clicked!

Explanation:
— Everything after => is the body of your "mini-function" that will be invoked when the event occurs.
— You don't need to worry about the handler's lifecycle: it lives as long as your subscription (+=). Important: if a lambda captures outer variables and you don't unsubscribe from the event, this can lead to memory leaks.

Lambda handler with captured variables

int clickCount = 0;

button.Click += (sender, args) =>
{
    clickCount++;
    Console.WriteLine($"The button has been clicked {clickCount} time(s)!");
};

button.SimulateClick(); // => The button has been clicked 1 time(s)!
button.SimulateClick(); // => The button has been clicked 2 time(s)!

What happens:
The lambda captures the external variable clickCount and remembers its value between calls.

3. Using lambda expressions for callbacks

Callback is often used when some code needs to call your function later — for example, after completing some long-running work.

Passing a lambda as a callback

Suppose we have a method that accepts a delegate or lambda as an argument:

void DoWork(Action callback)
{
    Console.WriteLine("Work is starting...");
    // Simulate work
    System.Threading.Thread.Sleep(500); // (Don't do this in real apps, this is blocking!) 
    callback(); // After work is done call the callback
}

DoWork(() => Console.WriteLine("Work finished!"));

Result:
First the console will show "Work is starting...", then "Work finished!".

You can pass lambdas with parameters:

void Calculate(int a, int b, Action<int> onResult)
{
    int sum = a + b;
    onResult(sum);
}

Calculate(5, 8, result => Console.WriteLine($"Result: {result}"));

4. Common mistakes when using lambdas

Not unsubscribing from events

When you subscribe to an event with a lambda, you don't have a "named handler" to easily unsubscribe later:

button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // How to pass your lambda here? You can't, unless it's saved.

If you really need to unsubscribe — store the lambda in a variable:

EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...then
button.Click -= handler;

This is important for long-lived objects: if you don't unsubscribe, the reference may remain and possibly cause a memory leak.

Capturing loop variables

Classic: subscribing to events inside a loop:

for (int i = 0; i < 3; i++)
    button.Click += (s, e) => Console.WriteLine(i); // All lambdas "remember" the same i

In modern C# this behavior is fixed for foreach, but there can still be nuances with for — always be careful with captured variables!

If you need each lambda to remember its own value — create a local copy:

for (int i = 0; i < 3; i++)
{
    int localI = i;
    button.Click += (s, e) => Console.WriteLine(localI);
}
2
Task
C# SELF, level 50, lesson 3
Locked
Click Counter with Variable Capture
Click Counter with Variable Capture
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION