Courses/C#/Events: Advanced Patterns

    Events: Advanced Patterns & Custom EventArgs

    Lesson 17 โ€ข Advanced Track

    What You'll Learn

    • Create custom EventArgs classes to carry rich data with events
    • Use EventHandler<T> for type-safe event declarations
    • Implement custom event accessors (add/remove) for thread safety
    • Build the Observer pattern using C# events
    • Manage event subscriptions and prevent memory leaks
    • Design event-driven architectures for decoupled systems

    ๐Ÿ’ก Real-World Analogy

    Events are like a notification system. A news publisher (event source) doesn't know or care who subscribes โ€” it just broadcasts when something happens. Subscribers (event handlers) sign up to receive notifications and can unsubscribe any time. Custom EventArgs are like the news article attached to each notification โ€” they carry the details of what happened.

    1. Custom EventArgs

    While simple events can pass EventArgs.Empty, real applications need to carry data. Create a class that extends EventArgs with properties for the data subscribers need โ€” order IDs, amounts, timestamps, etc.

    Custom EventArgs

    Create rich event data with OrderEventArgs and multiple subscribers.

    Try it Yourself ยป
    C#
    using System;
    
    // Custom EventArgs carry data about the event
    class OrderEventArgs : EventArgs
    {
        public string OrderId { get; }
        public decimal Amount { get; }
        public DateTime Timestamp { get; }
    
        public OrderEventArgs(string orderId, decimal amount)
        {
            OrderId = orderId;
            Amount = amount;
            Timestamp = DateTime.Now;
        }
    }
    
    class OrderService
    {
        // Declare event with custom EventArgs
        public event EventHandler<OrderEventArgs>? OrderPlaced;
        public
    ...

    2. Custom Event Accessors

    By default, C# auto-generates add and remove accessors for events. You can write custom accessors to add thread safety with lock, logging, or subscription validation โ€” similar to property get/set.

    Custom Event Accessors

    Implement thread-safe add/remove with logging.

    Try it Yourself ยป
    C#
    using System;
    using System.Collections.Generic;
    
    class EventBus
    {
        // Custom event accessors for thread-safe subscription
        private readonly object _lock = new object();
        private EventHandler<string>? _messageReceived;
    
        public event EventHandler<string> MessageReceived
        {
            add
            {
                lock (_lock)
                {
                    _messageReceived += value;
                    Console.WriteLine($"  โœ… Subscriber added (total: {_messageReceived?.GetInvocationList().Leng
    ...

    3. Observer Pattern with Events

    The Observer pattern is built into C# via events. A subject (sensor) notifies all observers (displays) when its state changes. Events provide natural decoupling โ€” the sensor doesn't import or know about the display classes.

    Observer Pattern

    Build a temperature monitoring system with multiple observers.

    Try it Yourself ยป
    C#
    using System;
    using System.Collections.Generic;
    
    // Observer pattern with IObservable/IObserver
    class TemperatureSensor
    {
        public event EventHandler<double>? TemperatureChanged;
    
        private double _temperature;
        public double Temperature
        {
            get => _temperature;
            set
            {
                if (Math.Abs(_temperature - value) > 0.01)
                {
                    _temperature = value;
                    TemperatureChanged?.Invoke(this, value);
                }
            }
        }
    }
    
    c
    ...

    Pro Tips

    • ๐Ÿ’ก Always use ?.Invoke(): Events can be null if no subscribers exist. MyEvent?.Invoke(this, args) prevents crashes.
    • ๐Ÿ’ก Events vs delegates: Events restrict external code to only += and -=. Delegates allow external code to invoke or reassign โ€” events are safer for public APIs.
    • ๐Ÿ’ก Unsubscribe to prevent leaks: If a short-lived object subscribes to a long-lived object's event, it won't be garbage collected. Always unsubscribe in Dispose().
    • ๐Ÿ’ก Use record for EventArgs: record OrderEventArgs(string Id, decimal Amount) : EventArgs; is concise in C# 9+.

    Common Mistakes

    • Invoking without null check: OrderPlaced(this, args) throws if no subscribers. Always use OrderPlaced?.Invoke(this, args).
    • Memory leaks from subscriptions: Lambda event handlers are hard to unsubscribe. Store them in a variable if you need to unsubscribe later.
    • Raising events from outside the class: Events can only be invoked from within the declaring class โ€” this is by design for encapsulation.
    • Exception in one handler stops others: If handler A throws, handlers B and C won't fire. Consider wrapping invocations in try-catch loops.

    ๐ŸŽ‰ Lesson Complete

    • โœ… Custom EventArgs carry rich data: order IDs, amounts, timestamps
    • โœ… EventHandler<T> provides type-safe event declarations
    • โœ… Custom add/remove accessors enable thread-safe subscription management
    • โœ… The Observer pattern is natively supported through C# events
    • โœ… Always use ?.Invoke() and unsubscribe to prevent memory leaks
    • โœ… Next lesson: Expression Trees โ€” building dynamic logic at runtime

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy โ€ข Terms of Service