JS
C#

JavaScript to C#

10 lessons

Progress0%
1Variables & Types2Classes & OOP3Async/Await4Array Methods → LINQ5Exception Handling6Collections7Generics8Delegates and Events9Records and Pattern Matching10File I/O
All Mirror Courses
JS
C#
Delegates and Events
MirrorLesson 8 of 10
Lesson 8

Delegates and Events

Type-safe function references, callbacks, and the event pattern

Introduction

In this lesson, you'll learn about delegates and events in C#. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
JS
From JavaScript:

In JavaScript, you're familiar with type-safe function references, callbacks, and the event pattern.

C#
In C#:

C# has its own approach to type-safe function references, callbacks, and the event pattern, which we'll explore step by step.

The C# Way

Let's see how C# handles this concept. Here's a typical example:

C#
C# Example
// Built-in delegate types
Action<string> greet = name => Console.WriteLine($"Hello {name}");
Func<int,int,int> add = (a, b) => a + b;
Predicate<int> isEven = x => x % 2 == 0;

// Multicast delegate (+=)
Action<string> log = Console.WriteLine;
log += s => File.AppendAllText("log.txt", s);
log("test"); // calls both!

// Event pattern
public class Button
{
    public event EventHandler<string> Clicked;

    public void Click(string label)
        => Clicked?.Invoke(this, label);
}

var btn = new Button();
btn.Clicked += (sender, label) => Console.WriteLine($"Clicked: {label}");
btn.Clicked += (_, label) => SaveToDb(label); // += adds handler
btn.Click("Submit");   // both handlers fire

// Func<T1,T2,...,TResult> — last type is return type
Func<string, int> length = s => s.Length;

Comparing to JavaScript

Here's how you might have written similar code in JavaScript:

JS
JavaScript (What you know)
// Functions are first-class
const add = (a, b) => a + b;
const greet = name => `Hello ${name}`;

// Callbacks
function run(fn, x) { return fn(x); }
run(greet, "Alice"); // "Hello Alice"

// Event emitter pattern
const emitter = new EventEmitter();
emitter.on("data", chunk => console.log(chunk));
emitter.emit("data", "hello");

// Array of callbacks
const handlers = [];
handlers.push(x => console.log(x));
handlers.forEach(h => h("event"));
Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C#
In C#:

Action<T> = void callback; Func<T,R> = function returning R; Predicate<T> = bool function

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C#
In C#:

Delegates are multicast — += adds handlers, -= removes them

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C#
In C#:

event keyword restricts delegate to += and -= only from outside the class

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C#
In C#:

EventHandler<T> is the standard event delegate: (object sender, T args)

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C#
In C#:

Nullable invoke: Clicked?.Invoke() safely handles no subscribers

Step-by-Step Breakdown

1. Action and Func

Use Action<T> for void callbacks and Func<T,R> for functions that return a value. These replace custom delegate declarations.

JS
JavaScript
const fn = x => x * 2;
C#
C#
Func<int,int> fn = x => x * 2;
Action<string> log = Console.WriteLine;

2. Multicast Delegates

You can chain multiple functions with +=. When invoked, all functions run in order.

JS
JavaScript
const handlers = [fn1, fn2]; handlers.forEach(h => h(x));
C#
C#
Action<string> chain = fn1;
chain += fn2;
chain("hello"); // fn1 and fn2 both called

3. Events

The event keyword wraps a delegate. External code can only += and -= handlers — it cannot invoke or replace the delegate.

JS
JavaScript
emitter.on("click", handler);
emitter.emit("click", data);
C#
C#
// In class:
public event Action<string> DataReceived;
// Trigger:
DataReceived?.Invoke("payload");
// Subscribe:
obj.DataReceived += data => Console.WriteLine(data);
Rule of Thumb
Always use event for public notifications — it prevents subscribers from clearing all handlers.

4. Standard EventHandler Pattern

EventHandler<TEventArgs> is the .NET convention for events. TEventArgs should inherit from EventArgs.

JS
JavaScript
emitter.on("change", (oldVal, newVal) => ...)
C#
C#
class ChangedEventArgs : EventArgs { public int OldValue, NewValue; }
public event EventHandler<ChangedEventArgs> ValueChanged;
// Fire:
ValueChanged?.Invoke(this, new ChangedEventArgs { OldValue=1, NewValue=2 });

Common Mistakes

When coming from JavaScript, developers often make these mistakes:

  • Action<T> = void callback; Func<T,R> = function returning R; Predicate<T> = bool function
  • Delegates are multicast — += adds handlers, -= removes them
  • event keyword restricts delegate to += and -= only from outside the class
Common Pitfall
Don't assume C# works exactly like JavaScript. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Action<T> for void; Func<T1,...,TResult> for returning; Predicate<T> for bool predicates
  • Delegates are multicast — += chains handlers, -= removes them
  • event restricts external code to only += and -=, preventing accidental .Invoke()
  • EventHandler<TEventArgs> is the standard .NET event signature pattern
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your JavaScript code in C# to practice these concepts.
PreviousNext