--- marp: true paginate: true math: mathjax theme: buutti title: N. Delegates and events --- # Delegates and events ## Overview * Delegates * Multicast Delegates * Anonymous Methods * Events ## Delegates * [Delegates](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/) are reference type variables that hold a *__reference to a method__* or multiple methods * Class objects hold a reference to a class instance * Delegate objects hold a reference to a method / methods * Similar to function pointers in C and C++, or how any function in JavaScript works * Allows for methods to be passed as variables, useful for creating, for example, events ### Creating a delegate * Declare a delegate using the following syntax: ```csharp delegate returnType DelegateName(parameters); ``` * Example: ```csharp delegate void PrintDelegate(string output); ``` * This creates a new delegate of type `void`, named `PrintDelegate` and one parameter of type `string` * The referenced method return and parameter types have to match the delegate! ### Referencing a delegate * After creating the delegate, it can be instantiated and the method assigned to it with the method name: ```csharp delegate void PrintDelegate(string output); static void Main(string[] args) { void PrintInLower(string text) { Console.WriteLine(text.ToLower()); } PrintDelegate print = PrintInLower; print("AaaBbbCcc"); // Outputs "aaabbbccc" } ``` ### Using multicast delegates * Delegates can be composed of multiple methods using the `+` operator * Using the same `PrintDelegate` delegate as before, we could do this:
```csharp delegate void PrintDelegate(string output); static void Main(string[] args) { void PrintInLower(string text) { Console.WriteLine(text.ToLower()); } void PrintInUpper(string text) { Console.WriteLine(text.ToUpper()); } PrintDelegate print = PrintInLower; print += PrintInUpper; print("AaaBbbCcc"); } ```
![](imgs/11%20Delegates%20and%20Events_1.png)
### Removing methods * Methods can be removed from the delegate with the `-` operator: ```csharp delegate void PrintDelegate(string output); static void Main(string[] args) { ... print -= PrintInLower; print("AaaBbbCcc"); // Outputs "AAABBBCCC" ... } ``` ### Delegates: An example * Let's extend our previous example (without the `-=` part) by creating a new class called `DelegateTest`, and giving it a constructor that takes a `PrintDelegate` object as a parameter: ```csharp public class DelegateTest { public DelegateTest(PrintDelegate printDelegate) { printDelegate("This Method Was Called From Another Class!"); } } ``` ---
```csharp void PrintInLower(string text) { Console.WriteLine(text.ToLower()); } void PrintInUpper(string text) { Console.WriteLine(text.ToUpper()); } // Initialize new delegate which is composed of PrintInLower method PrintDelegate print = PrintInLower; // Add PrintInUpper method to the delegate print += PrintInUpper; // Send the delegate to the class constructor DelegateTest delegateTest = new DelegateTest(print); ```
Now we can create a new DelegateTest object and pass the delegate to the object constructor: ![](imgs/11%20Delegates%20and%20Events_2.png)
### Anonymous methods * Delegates can be initialized anonymously (without a specified name) * Anonymous method in variable declaration: ```csharp delegate void PrintDelegate(string output); bool printUpper = true; PrintDelegate printCheckUpper = delegate (string text) { if (printUpper) Console.WriteLine(text.ToUpper()); else Console.WriteLine(text); }; printCheckUpper("I'm not angry!"); // Outputs I'M NOT ANGRY! ``` * Notice that the actual method that prints the text is not declared anywhere! --- * You can use an empty anonymous method to initialize a delegate that does nothing: ```csharp delegate void SomeDelegate(); class Program { static void Main(string[] args) { // Initialize an empty delegate, add method later... SomeDelegate myDelegate = new SomeDelegate(delegate { }); } } ``` ## Events ### The problem
```csharp class Game { Sound gameOverSound; Window gameOverScreen; void OnGameOver() { gameOverSound.Play(); // plays some sound gameOverScreen.Show(); // shows a screen } } ... class Program { static void Main() { var game = new Game(); // somewhere in the game logic... // game.OnGameOver(); } } ```
* Consider a game engine with three classes, `Sound`, `Window` and `Game`. * If implemented like this, the `Game` class has to know about the `Sound` and `Window` classes * $\Rightarrow$ `Game` is ***tightly coupled***, and thus ***dependent*** on the `Sound` and `Window` classes * Changes in either of the classes could break the code!
### The solution: Events * A solution to this problem is the [Publisher-subscriber pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) * In C#, this pattern is implemented with the [`event` keyword](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/event?redirectedfrom=MSDN) * Events are ***signals*** that are raised by a *__Publisher__* and received by a *__Subscriber__* * The publisher does not know or care who, if anyone, receives the signal * Changes in the subscriber classes do not affect the publisher * In C#, events are multicast delegates * When an object triggers an event, the event invokes ***event handlers*** * Event handlers are delegate instances added to the event ### Raising events * Events consist of two elements: * A delegate that identifies the method that provides the response to the event * In the simplest case, we can use the built-in [`EventHandler`](https://learn.microsoft.com/en-us/dotnet/api/system.eventhandler?view=net-9.0) delegate * An optional class to hold event data if the event provides data. ```csharp public class Publisher { public event EventHandler SampleEvent; // Wrap the event in a protected virtual method // to enable derived classes to raise the event. protected virtual void OnSampleEvent() { // Raise the event in a thread-safe manner using the ?. operator. SampleEvent?.Invoke(this); } } ``` ### Raising the event Events can only be invoked (i.e., raised) from within the class (or derived classes) or struct where they're declared (the publisher class)
```csharp public class Publisher { public event EventHandler SampleEvent; protected virtual void OnSampleEvent() { SampleEvent?.Invoke(this); } } ```
```csharp class Program { static void Main() { var pub = new Publisher(); pub.OnSampleEvent(); // ✅ Works fine // pub.SampleEvent(); // ❌ Not allowed! } } ```
### Adding subscribers to the event * Now that we know how to raise the event, we can add subscribers to it * i.e., functions that get called when the event is raised
```csharp public class Publisher { public event EventHandler SampleEvent; protected virtual void OnSampleEvent() { SampleEvent?.Invoke(this); } } ```
```csharp {7-8} class Program { static void Main() { var pub = new Publisher(); pub.SampleEvent += () => Console.WriteLine("Sample event!"); pub.OnSampleEvent(); } } ```
### Custom event handler * We can define the delegate ourselves, for example if we want to send some data to the event. ```csharp public class Publisher { public delegate void SampleEventHandler(object sender); // Declare the event. public event SampleEventHandler SampleEvent; protected virtual void OnSampleEvent() { SampleEvent?.Invoke(this); } } ``` ### Event arguments * Finally, we can send arguments to the event like this: ```csharp public class SampleEventArgs { public SampleEventArgs(string text) { Text = text; } public string Text { get; } // readonly } public class Publisher { public delegate void SampleEventHandler(object sender, SampleEventArgs e); public event SampleEventHandler SampleEvent; protected virtual void RaiseSampleEvent() { SampleEvent?.Invoke(this, new SampleEventArgs("Hello")); } } ``` ### "Fixing" the Game Over example
```csharp public delegate void GameOverHandler(); class Game { public event GameOverHandler GameOver; protected virtual void OnGameOver() { GameOver?.Invoke(this); } } ... class Program { static void Main() { var game = new Game(); Sound gameOverSound; Window gameOverScreen; game.GameOver += gameOverSound.Play; game.GameOver += gameOverScreen.Show; } } ```
1) Declare an event handler delegate (`GameOverHandler`) 2) Declare an instance of the handler with the `event` keyword (`GameOVer`) 3) Declare a virtual method that invokes the event 4) Add methods that subscribe to the event
## Exercise 1: A rudimentary event system Create a console application for controlling a plant treatment system with three methods that print the following outputs: | Method | Output | | :-- | :-- | | `void ReleaseWater()` | `Releasing water...` | | `void ReleaseFertilizer()` | `Releasing fertilizer...` | | `void IncreaseTemperature()` | `Increasing temperature...` | * All methods are off by default. Create a main loop where the user can... * ...type the name of the method to switch each method on (add it to the delegate) * ...type `run` to execute all the methods that are on --- * ***Hint:*** you can just use switch-case for defining which method should be added to the delegate * Here's an example console input & output: ![](imgs/11%20Delegates%20and%20Events_3.png)