The content below is retrieved from Jupyter notebooks that you can find in this repository.

Source: Microsoft documentation.


Events enable a class or object (the publisher) to notify other classes or objects (the subscribers) when something occurs.

  • The publisher determines when to raise an event. It can have multiple subscribers.
  • The subscriber determines how to handle an event when it’s raised. It can be subscribed to multiple events.

System.EventHandler is a delegate to handle an event that has no event data. Its signature is:

public delegate void EventHandler(object? sender, EventArgs e);

It’s equivalent to:

public Action<object?, EventArgs> ActionEventHandler;

System.EventArgs provides a value to use for events with no data, but it also represents the base class for events with data, e.g.

public class MyEventArgs : EventArgs {
    public string Message { get; }
    public DateTime DateTime { get; }

    public MyEventArgs(string message) {
        Message = message;
        DateTime = DateTime.Now;
    }
}

It’s possible to define a custom event handler if we have custom event data, like the class defined above:

public delegate void DefinedEventHandler(object sender, MyEventArgs args);

which can also be defined using generics with System.Action class.

Events are declared with the event keyword in the publisher class:

public event DefinedEventHandler DefinedEvent;

There’s an option to use generic types for custom event data so it’s not necessary to define a new delegate, as shown below:

public class Publisher {
    public event EventHandler<MyEventArgs> MyEvent;

    public void SendMessage(string message) {
        var eventArgs = new MyEventArgs(message);
        OnRaiseMyEvent(eventArgs);
    }

    public void OnRaiseMyEvent(MyEventArgs args) {
        // use a copy of the event to avoid a race condition
        var raiseEvent = MyEvent;

        if (raiseEvent is null) {
            // event is null if it doesn't have subscribers
            return;
        }

        // raise the event by invoking it as calling a method
        raiseEvent(this, args);
    }
}
public class Subscriber {
    public int Id { get; }

    public Subscriber(int id, Publisher publisher) {
        Id = id;
        // the event gets a new subscriber, which is a method to handle it
        publisher.MyEvent += HandleMyEvent;
    }

    /// The parameters of this method match the signature of Publisher.MyEvent
    public void HandleMyEvent(object sender, MyEventArgs args) {
        Console.WriteLine($"Subscriber {Id} received the following message:\n{args.Message}\nAt {args.DateTime}");
    }
}
var publisher = new Publisher();
var subscribers = Enumerable.Range(1, 3)
    .Select(i => new Subscriber(i, publisher))
    .ToList();
publisher.SendMessage("Good afternoon to all!");
Subscriber 1 received the following message:
Good afternoon to all!
At 1/21/2022 6:47:40 PM

Subscriber 2 received the following message:
Good afternoon to all!
At 1/21/2022 6:47:40 PM

Subscriber 3 received the following message:
Good afternoon to all!
At 1/21/2022 6:47:40 PM

Events are a special type of multicast delegates that can only be invoked from within the class that declares them. In other words, only the publishers can raise their own events.