Type System, Conversions & Boxing

Page content

🔥 Delegates & Events (Deep Dive)

1 What Is a Delegate Really?

A delegate is:

A type-safe function pointer object.

It is a reference type that holds:

  • Method pointer
  • Target object (for instance methods)

Example:

delegate int Add(int a, int b);

Add add = (x, y) => x + y;

Console.WriteLine(add(2, 3)); // 5

Internally, add is an object containing:

  • Method metadata
  • Possibly a reference to a target instance

2 Delegates Are Objects

Add a = (x, y) => x + y;

Console.WriteLine(a.GetType());

Delegates inherit from System.MulticastDelegate.

They are reference types.


3 Multicast Delegates

Delegates can hold multiple methods.

void A() => Console.WriteLine("A");
void B() => Console.WriteLine("B");

Action act = A;
act += B;

act();

Output:

A
B

Invocation order is the order of subscription.


Important Rule

Delegates are immutable.

When you do:

act += B;

A new delegate instance is created.

Original delegate remains unchanged.


4 Multicast + Return Values (Danger Area)

What happens here?

int M1() { return 1; }
int M2() { return 2; }

Func<int> f = M1;
f += M2;

Console.WriteLine(f());

Output?

2

Why?

Because for multicast delegates with return values:

  • All methods execute
  • Only the last return value is returned

5 Exception Behavior

If one method throws:

void A() { Console.WriteLine("A"); }
void B() { throw new Exception(); }
void C() { Console.WriteLine("C"); }

Action act = A;
act += B;
act += C;

act();

Execution stops at B.

C is never executed.

Delegates do not automatically continue after exception.


6 Removing Methods (-=)

act -= B;

Removes last matching occurrence from invocation list.

If multiple identical methods exist, only the last one is removed.


7 Delegates and Equality

Two delegates are equal if:

  • Same method
  • Same target
  • Same invocation list order

Example:

Action a1 = A;
Action a2 = A;

Console.WriteLine(a1 == a2); // True

But:

Action a1 = A;
Action a2 = B;

Console.WriteLine(a1 == a2); // False

Multicast order matters:

A + B ≠ B + A


8 Instance Method Delegates

class Test
{
    public void Show() => Console.WriteLine("Hi");
}

var t1 = new Test();
var t2 = new Test();

Action a1 = t1.Show;
Action a2 = t2.Show;

Console.WriteLine(a1 == a2); // False

Why?

Same method, different target instance.

Delegate equality checks both method and target.


9 Anonymous Methods & Lambdas

Each lambda creates a new delegate instance.

Action a1 = () => Console.WriteLine("Hi");
Action a2 = () => Console.WriteLine("Hi");

Console.WriteLine(a1 == a2); // False

Even though body is identical.

Because compiler generates separate method instances.


10 Closures

int x = 10;

Action act = () => Console.WriteLine(x);

x = 20;

act();

Output?

20

Why?

Lambdas capture variables, not values.

Compiler creates a hidden closure class.

All lambdas referencing same variable share that instance.


11 Classic Closure Trap

var actions = new List<Action>();

for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.WriteLine(i));
}

foreach (var a in actions)
{
    a();
}

Output?

3 3 3

Why?

All lambdas capture the same i.

Fix:

for (int i = 0; i < 3; i++)
{
    int temp = i;
    actions.Add(() => Console.WriteLine(temp));
}

Now output: 0 1 2


12 Events

An event is a delegate with restricted access.

public event Action OnSomething;

Outside the class:

  • You can only use += or -=
  • You cannot invoke it

Inside the class:

  • You can invoke it

Why Events Exist

To prevent external code from doing:

obj.OnSomething = null;  // illegal

Events protect invocation list.


13 Safe Event Invocation Pattern

OnSomething?.Invoke();

Prevents NullReferenceException.


14 Event Backing Field

Compiler generates a private delegate field behind the event.

Events are syntactic sugar over delegates.


15 Variance with Delegates

Covariant return:

Func<string> f1 = () => "Hello";
Func<object> f2 = f1;  // Allowed

Contravariant parameter:

Action<object> a1 = x => {};
Action<string> a2 = a1; // Allowed

Delegates support variance automatically.


16 Invocation List

You can inspect:

Delegate[] list = act.GetInvocationList();

Returns array of delegates.

Used internally in multicast behavior.


17 Delegates vs Func / Action

Built-in generic delegates:

  • Action → void return
  • Func → returns T
  • Predicate → returns bool

Prefer these over custom delegates in modern code.


Final Mental Model

Delegates are:

  • Objects
  • Immutable
  • Multicast-capable
  • Equality-sensitive
  • Closure-enabled
  • Variance-aware

Events are:

  • Delegate wrappers
  • Invocation-protected