C# Programming Tutorial 0/45 lessons ~6 min read Lesson 34

    Events

    Events wrap delegates with publish-subscribe semantics — only the declaring class can raise (invoke) them; external code subscribes with +=.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Events wrap delegates with publish-subscribe semantics — only the declaring class can raise (invoke) them; external code subscribes with +=. WPF, WinForms, and domain-driven designs use events for loose coupling.

    EventHandler and EventHandler<TEventArgs> are standard patterns. Interviewers warn about memory leaks when subscribers forget -= and thread safety of raise.

    The story

    When a customer completes checkout on an e-commerce site, the order service raises an OrderPlaced event so inventory, email, and analytics modules react without the order service knowing about them directly. Email sends a confirmation; warehouse reserves stock — all subscribed via += handlers.

    Understanding the topic

    Key concepts

    • event keyword restricts invoke to containing class.
    • EventHandler standard signature.
    • Custom EventArgs carry payload properties.
    • Nullable event — check null before invoke.
    • Record EventArgs for immutability.
    • Async events rare — prefer IObservable or channels.

    Step-by-step explanation

    1. Publisher declares public event EventHandler SomethingHappened;
    2. Subscriber registers handler += OnSomething;
    3. Publisher calls SomethingHappened?.Invoke(this, args);
    4. Unsubscribe -= to release reference.
    5. Thread-safe raise may copy delegate to local var.
    6. Domain events in DDD before integration events.

    Practical code example

    Order placed domain event with typed EventArgs:

    csharp
    namespace TechLearningPro.Events;
    public sealed class OrderPlacedEventArgs(Guid orderId, decimal total) : EventArgs
    {
    public Guid OrderId { get; } = orderId;
    public decimal Total { get; } = total;
    }
    public sealed class OrderService
    {
    public event EventHandler<OrderPlacedEventArgs>? OrderPlaced;
    public void PlaceOrder(Guid orderId, decimal total)
    {
    // persist order...
    OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(orderId, total));
    }
    }

    Line-by-line code explanation

    • OrderPlacedEventArgs(Guid orderId, decimal total) : EventArgs carries event payload immutably.
    • public Guid OrderId { get; } and Total { get; } expose read-only event data.
    • event EventHandler<OrderPlacedEventArgs>? OrderPlaced declares the event — external code can subscribe but not invoke.
    • PlaceOrder(Guid orderId, decimal total) is the publisher method that raises the event after persisting.
    • OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(orderId, total)) safely notifies all subscribers.
    • ?. before Invoke skips the call when no handlers are registered — no null check needed.
    • this as sender identifies which service raised the event.
    • new OrderPlacedEventArgs(...) creates fresh args for each notification.

    Key takeaway: ?.Invoke safe if no subscribers. In ASP.NET prefer MediatR notifications over events for request-scoped DI.

    Real-world use

    Where you'll use this in production

    • UI button click handlers (legacy desktop).
    • Domain events notifying read model updaters.
    • FileSystemWatcher Changed event.
    • Timer Elapsed notifications.

    Best practices

    • Always unsubscribe in IDisposable/dispose pattern.
    • Use weak events for long-lived publishers (WPF).
    • Prefer MediatR/IHostedService in ASP.NET over static events.
    • Make EventArgs immutable.
    • Copy event delegate before invoke if multi-threaded.

    Common mistakes

    • Public invoke on event from outside — compile error, use method.
    • Memory leak forgetting -=.
    • Heavy work in UI event handler blocking thread.
    • Throwing from multicast stops other handlers.

    Advanced interview questions

    Q1BeginnerEvent vs delegate field?
    Event restricts external invoke — encapsulation.
    Q2BeginnerStandard EventHandler signature?
    (object sender, EventArgs e).
    Q3IntermediateEvent memory leak scenario?
    Long-lived publisher holds subscriber via delegate — unsubscribe.
    Q4IntermediateDomain events vs integration events?
    Domain: in-process aggregate side effects; integration: cross-service async messages.
    Q5AdvancedReplace static events in ASP.NET with DI-friendly pattern.
    MediatR INotification; or IHostedService Channel; scoped handlers per request.

    Summary

    Events encapsulate delegate publish-subscribe. Only publisher raises; subscribers += and -=. Unsubscribe to prevent memory leaks. In modern web prefer MediatR over static events. Next: lambda expressions.

    Ready to mark this lesson complete?Track your journey across the entire course.