C# delegates, events, and lambdas

Pass behaviour as a value and let objects notify subscribers when something happens.

C#
using System;

class Button {
  public event Action<string> Clicked;

  public void Click(string user) {
    Clicked?.Invoke(user);
  }
}

class Program {
  static void Main() {
    Func<int, int> square = x => x * x;
    Func<int, int> addOne = x => x + 1;
    Func<int, int> squareThenAddOne = x => addOne(square(x));
    Console.WriteLine(square(5) + " " + squareThenAddOne(5));

    var button = new Button();
    int clicks = 0;
    Action<string> counter = user => clicks++;
    Action<string> greeter = user => Console.WriteLine("hello " + user);

    button.Clicked += counter;
    button.Clicked += greeter;
    button.Click("Ada");
    button.Clicked -= greeter;
    button.Click("Bo");
    Console.WriteLine("clicks: " + clicks);
  }
}
Output
25 26
hello Ada
clicks: 2

Explanation

A delegate is a type that represents a method with a given signature. Func<TArg, TResult> and Action<TArg> are generic delegates for functions that return a value and for functions that do not. A lambda expression, such as x => x * x, creates one in place. Functions passed as arguments make code flexible: a method can take a Func that decides how to compute something.

An event is a delegate that outside code can subscribe to with += and unsubscribe from with -=, but cannot invoke or reset. The class that declares it raises the event with ?.Invoke, which does nothing if there are no subscribers. This is the observer pattern built into the language. The publisher does not know who is listening.

Explore the dry run
Guided walkthroughReady
1using System;
2
3class Button {
4 public event Action<string> Clicked;
5
6 public void Click(string user) {
7 Clicked?.Invoke(user);
8 }
9}
10
11class Program {
12 static void Main() {
13 Func<int, int> square = x => x * x;
14 Func<int, int> addOne = x => x + 1;
15 Func<int, int> squareThenAddOne = x => addOne(square(x));
16 Console.WriteLine(square(5) + " " + squareThenAddOne(5));
17
18 var button = new Button();
19 int clicks = 0;
20 Action<string> counter = user => clicks++;
21 Action<string> greeter = user => Console.WriteLine("hello " + user);
22
23 button.Clicked += counter;
24 button.Clicked += greeter;
25 button.Click("Ada");
26 button.Clicked -= greeter;
27 button.Click("Bo");
28 Console.WriteLine("clicks: " + clicks);
29 }
30}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
A lambda stored in a delegate variable.—
—
Delegates can be combined by calling one inside another.—
25 26
Subscribe a handler to the event.—
25 26
Both handlers run: the counter increments clicks and the greeter prints.clicks = 1
25 26
hello Ada
Unsubscribe the greeter.clicks = 1
25 26
hello Ada
Only the counter runs now, so nothing is printed.clicks = 2
25 26
hello Ada
Two clicks were counted.clicks = 2
25 26
hello Ada
clicks: 2

Cost and common mistakes

Raising an event costs O(S) for S subscribers.

  • Raising an event with no subscribers throws a NullReferenceException unless you use ?.Invoke.
  • A subscriber that is never removed stays reachable from the publisher and cannot be garbage collected. Unsubscribe when you are done.
  • A lambda captures variables, not values. Changing a captured variable later changes what the lambda sees.

Try it yourself

Why is the event keyword used instead of a public delegate field?

Reveal answer and reasoning

With a plain public delegate, outside code could invoke it or replace all subscribers with =. An event only allows += and -=, so the publisher stays in control.

Further reading