PY
C#

Python to C#

10 lessons

Progress0%
1Variables & Types2Classes & OOP3Collections & LINQ4Async/Await5Exception Handling6File I/O7Generics8Delegates and Events9Records and Pattern Matching10Interfaces
All Mirror Courses
PY
C#
Delegates and Events
MirrorLesson 8 of 10
Lesson 8

Delegates and Events

Action/Func vs Python callables, and the event notification pattern

Introduction

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

Mirror Card
PY
From Python:

In Python, you're familiar with action/func vs python callables, and the event notification pattern.

C#
In C#:

C# has its own approach to action/func vs python callables, and the event notification 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 (no custom delegate declarations needed)
Func<int, int> double_ = x => x * 2;
Action<string> log = Console.WriteLine;
Predicate<int> isEven = x => x % 2 == 0;

// Higher-order function
int Apply(Func<int, int> fn, int val) => fn(val);
Apply(double_, 5);         // 10
Apply(x => x + 1, 5);     // 6

// Multicast delegate — += chains handlers
Action<string> handlers = Console.WriteLine;
handlers += s => File.AppendAllText("log.txt", s);
handlers("event");         // both run

// Event pattern — proper encapsulation
public class Button
{
    // event restricts external code to += and -=
    public event Action<string>? Clicked;

    public void Click(string label)
        => Clicked?.Invoke(label);  // null-safe invoke
}

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

Comparing to Python

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

PY
Python (What you know)
from typing import Callable

# Functions as values
def double(x: int) -> int:
    return x * 2

# Higher-order functions
def apply(fn: Callable[[int], int], val: int) -> int:
    return fn(val)

apply(double, 5)  # 10
apply(lambda x: x + 1, 5)  # 6

# Callback list pattern
handlers: list[Callable[[str], None]] = []
handlers.append(print)
handlers.append(lambda s: open("log.txt","a").write(s))

for h in handlers:
    h("event")

# Observer pattern
class Button:
    def __init__(self):
        self._on_click: list[Callable[[str], None]] = []

    def on_click(self, handler: Callable[[str], None]):
        self._on_click.append(handler)

    def click(self, label: str):
        for h in self._on_click:
            h(label)

btn = Button()
btn.on_click(lambda l: print(f"Clicked: {l}"))
btn.click("Submit")
Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

Action<T> = void callback (Callable[[T], None]); Func<T,R> = returning function

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

Delegates are multicast — += chains multiple handlers (Python: list of callables)

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

event keyword prevents external code from invoking or replacing the delegate

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

Clicked?.Invoke() safely handles no subscribers (vs Python: if handlers: ...)

Mirror Card
PY
From Python:

You may be used to different syntax or behavior.

C#
In C#:

Lambda syntax: x => expr in C# vs lambda x: expr in Python

Step-by-Step Breakdown

1. Action and Func

Use built-in Action/Func types instead of custom delegates. Action is void; Func's last type parameter is the return type.

PY
Python
fn: Callable[[int], int]
void_fn: Callable[[str], None]
C#
C#
Func<int, int> fn;           // int → int
Action<string> voidFn;       // string → void
Predicate<int> pred;         // int → bool

2. Multicast Delegates

+= chains multiple function references. All are called when the delegate is invoked — like calling all functions in a Python list.

PY
Python
handlers = [fn1, fn2]
for h in handlers: h(x)
C#
C#
Action<string> chain = fn1;
chain += fn2;
chain("hello"); // fn1 and fn2 called

3. Events

The event keyword wraps a delegate for safe publish/subscribe. External code can only += (subscribe) and -= (unsubscribe).

PY
Python
class Emitter:
    def on(self, handler): self._h.append(handler)
    def emit(self, data): [h(data) for h in self._h]
C#
C#
class Emitter {
    public event Action<string>? DataReceived;
    public void Emit(string data) => DataReceived?.Invoke(data);
}
var e = new Emitter();
e.DataReceived += Console.WriteLine;
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. TEventArgs inherits from EventArgs. Sender is the object that raised the event.

PY
Python
def on_change(old, new): ...
C#
C#
class ChangedArgs : EventArgs { public int Old, New; }
public event EventHandler<ChangedArgs>? Changed;
Changed?.Invoke(this, new ChangedArgs { Old=1, New=2 });

Common Mistakes

When coming from Python, developers often make these mistakes:

  • Action<T> = void callback (Callable[[T], None]); Func<T,R> = returning function
  • Delegates are multicast — += chains multiple handlers (Python: list of callables)
  • event keyword prevents external code from invoking or replacing the delegate
Common Pitfall
Don't assume C# works exactly like Python. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Action<T> for void callbacks; Func<T,R> for returning functions — no custom delegate declarations
  • Multicast: += adds handlers, -= removes; invoke calls all in order
  • event keyword: only += and -= allowed from outside class — use for public notifications
  • EventHandler<TEventArgs> is the .NET convention for (sender, args) events
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your Python code in C# to practice these concepts.
PreviousNext