<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Functional C# and LINQ
All lessons in this course

Lesson 39 of 60 13 minIntermediate

Events

Let objects announce that something happened without knowing who is listening: declaring, raising, subscribing to and unsubscribing from events.

When a model is saved, the viewport should refresh, the status bar should update and a backup might run. The Model class shouldn’t need to know about any of them. Events let one object (the publisher) announce “this happened” and any number of other objects (subscribers) react.

Interactive

Publisher and subscribers

Subscribe some objects to model.Saved, then call Save(). The Model never knows who is listening.

Modelpublic event EventHandler<SavedEventArgs>? Saved;
Viewport
model.Saved += OnViewportSaved
StatusBar
model.Saved += OnStatusBarSaved
AutoBackup
model.Saved − OnAutoBackupSaved
Two objects are subscribed. Press model.Save().

Declaring and raising an event

Model.cs
public class SavedEventArgs : EventArgs{    public string Path { get; }    public SavedEventArgs(string path) => Path = path;} public class Model{    public event EventHandler<SavedEventArgs>? Saved;   // the event     public void Save(string path)    {        // … write the file …        OnSaved(new SavedEventArgs(path));    }     protected virtual void OnSaved(SavedEventArgs e)        => Saved?.Invoke(this, e);                       // raise it (if anyone listens)}

Subscribing

Subscribe.cs
var model = new Model(); model.Saved += (sender, e) => Console.WriteLine($"Status: saved to {e.Path}");model.Saved += OnSavedBackup; model.Save("tower.ifc"); model.Saved -= OnSavedBackup;          // unsubscribemodel.Save("tower-v2.ifc"); static void OnSavedBackup(object? sender, SavedEventArgs e)    => Console.WriteLine($"Backup: copying {e.Path}");
Output
Status: saved to tower.ifc
Backup: copying tower.ifc
Status: saved to tower-v2.ifc

The standard pattern

  • Use EventHandler (no data) or EventHandler<TEventArgs> (with data).
  • Handlers receive (object? sender, TEventArgs e): who raised it, and the details.
  • Event-data classes derive from EventArgs and end in EventArgs.
  • Raise through a protected virtual void OnXxx(…) method so subclasses can extend it.
  • Name events with a verb: Saved, SelectionChanged, Clicked (past tense for “after”, -ing like Closing for “before”).

Why “event” and not a public delegate?

The event keyword restricts outside code to += and -=. Other classes can’t raise the event themselves or wipe everyone else’s subscriptions with =. Try the two buttons in the diagram to see the errors.

Events in desktop apps

Wpf.xaml.cs
// WPF / WinForms controls expose events for user actionssaveButton.Click += (sender, e) => model.Save(currentPath);membersList.SelectionChanged += OnSelectionChanged;this.Closing += (s, e) => { if (HasUnsavedChanges) e.Cancel = !ConfirmClose(); };
Quick check

From outside the Model class, which line compiles?

Quick check

What are the two parameters of a standard event handler?

Practice

Create a class Thermometer with a Temperature property. When it is set above 40, raise an event Overheated carrying the temperature. Subscribe a handler that prints a warning and set the temperature to 35, then 42.

Show the solution
Solution.cs
var t = new Thermometer();t.Overheated += (_, e) => Console.WriteLine($"Warning: {e.Value} °C!");t.Temperature = 35;t.Temperature = 42; public class ValueEventArgs : EventArgs{    public double Value { get; }    public ValueEventArgs(double v) => Value = v;} public class Thermometer{    public event EventHandler<ValueEventArgs>? Overheated;    private double _temp;     public double Temperature    {        get => _temp;        set        {            _temp = value;            if (value > 40) Overheated?.Invoke(this, new ValueEventArgs(value));        }    }}
Output
Warning: 42 °C!

Remember

  • Events let a publisher notify any number of subscribers without knowing them.
  • Declare with event EventHandler<T>; raise with Xxx?.Invoke(this, e).
  • Subscribers use += and -=; outside code can’t raise or reset the event.
  • Unsubscribe when done to avoid memory leaks.
0 of 60 lessons completed