How it works
An event lets an object notify interested listeners without knowing their concrete types. The publisher controls when the event is raised, while subscribers provide compatible handlers. A small Action-based event is enough to demonstrate the relationship, although a larger public API may use EventHandler<TEventArgs> to provide a more conventional event contract.
Subscriptions have a lifetime. The publisher retains its subscribed delegates, and those delegates can retain listener objects or captured state. When a listener no longer needs updates, unsubscribe using the same delegate identity. This is a relationship between objects, not a background message bus or an automatic thread switch. A normal event handler runs synchronously when the publisher invokes it unless the implementation explicitly does something else.
Walk through the example. The Counter raises Changed after adding a value. The named Handle method increments an observation count and prints the new total. After the first addition, the code unsubscribes that same handler. A second addition changes the counter's total but produces no event output for the former listener. The final values show a total of six and only one observed notification.
Watch the boundary. Subscribing with one anonymous lambda and unsubscribing with a newly written lambda does not reliably identify the original delegate instance. Store the handler when later unsubscription is required, or use a named method with the appropriate target. Also define how handler failures affect the publisher: an exception from a synchronous handler can interrupt the event invocation and the surrounding operation.
Practice independently. Add two listeners, unsubscribe one, and predict which notifications each receives after several updates. Use a stored delegate for one subscription and a named method for the other. Then define whether adding zero should raise an event, and test that policy. Explain why a listener's lifetime should be tied to a clear owner rather than relying on eventual garbage collection to end the subscription.
A working example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
var counter = new Counter();
int observed = 0;
void Handle(int total) { observed++; Console.WriteLine($"changed:{total}"); }
counter.Changed += Handle;
counter.Add(2);
counter.Changed -= Handle;
counter.Add(4);
Console.WriteLine(counter.Total);
Console.WriteLine(observed);
sealed class Counter
{
public int Total { get; private set; }
public event Action<int>? Changed;
public void Add(int amount) { Total += amount; Changed?.Invoke(Total); }
}
Expected output
changed:2 6 1
Unsubscribe the same handler identity, and remember that ordinary event invocation is synchronous.