finish lecture 11, fix theme not being applied in cli html conversion
- update readmemain
parent
0f0335833e
commit
88c8c953c6
File diff suppressed because one or more lines are too long
@ -1,289 +1,448 @@
|
|||||||
# Delegates and Events
|
---
|
||||||
|
marp: true
|
||||||

|
paginate: true
|
||||||
|
math: mathjax
|
||||||
|
theme: buutti
|
||||||
|
title: N. Delegates and events
|
||||||
---
|
---
|
||||||
|
|
||||||
# Overview
|
# Delegates and events
|
||||||
|
|
||||||
Delegates
|
|
||||||
|
|
||||||
Multicast Delegates
|
<!-- headingDivider: 5 -->
|
||||||
|
<!-- class: invert -->
|
||||||
|
|
||||||
Anonymous Methods
|
## Overview
|
||||||
|
|
||||||
Events
|
* Delegates
|
||||||
|
* Multicast Delegates
|
||||||
|
* Anonymous Methods
|
||||||
|
* Events
|
||||||
|
|
||||||
# Delegates
|
## Delegates
|
||||||
|
|
||||||
* __Delegates __ are reference type variables that hold a __reference to a method__ or multiple methods
|
* *__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
|
* 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
|
* 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
|
* Allows for methods to be passed as variables, useful for creating, for example, events
|
||||||
|
|
||||||
# Creating a Delegate
|
### Creating a delegate
|
||||||
|
|
||||||
Declare a delegate using the following syntax:
|
* Declare a delegate using the following syntax:
|
||||||
|
```csharp
|
||||||
delegate \<return type> \<delegate name>(\<parameters>);
|
delegate returnType DelegateName(parameters);
|
||||||
|
```
|
||||||
Example:
|
* Example:
|
||||||
|
|
||||||
delegate void PrintDelegate(string output);
|
```csharp
|
||||||
|
delegate void PrintDelegate(string output);
|
||||||
This creates a new delegate of type void, named PrintDelegate and one parameter of type string
|
```
|
||||||
|
* 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!
|
* The referenced method return and parameter types have to match the delegate!
|
||||||
|
|
||||||
# Referencing a Delegate
|
### Referencing a delegate
|
||||||
|
|
||||||
After creating the delegate, it can be instantiated and the method assigned to it with the method name:
|
* After creating the delegate, it can be instantiated and the method assigned to it with the method name:
|
||||||
|
```csharp
|
||||||
delegate void PrintDelegate(string output);
|
delegate void PrintDelegate(string output);
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
{
|
void PrintInLower(string text)
|
||||||
|
{
|
||||||
void PrintInLower(string text)
|
Console.WriteLine(text.ToLower());
|
||||||
|
}
|
||||||
{
|
|
||||||
|
PrintDelegate print = PrintInLower;
|
||||||
Console.WriteLine(text.ToLower());
|
print("AaaBbbCcc"); // Outputs "aaabbbccc"
|
||||||
|
}
|
||||||
}
|
```
|
||||||
|
|
||||||
PrintDelegate print = PrintInLower;
|
### Using multicast delegates
|
||||||
|
|
||||||
print("AaaBbbCcc"); // Outputs "aaabbbccc"
|
* Delegates can be composed of multiple methods using the `+` operator
|
||||||
|
* Using the same `PrintDelegate` delegate as before, we could do this:
|
||||||
}
|
<div class='columns21' markdown='1'>
|
||||||
|
<div markdown='1'>
|
||||||
# Using Multicast Delegates
|
|
||||||
|
```csharp
|
||||||
Delegates can be __composed of__ multiple methods using the "+" operator
|
delegate void PrintDelegate(string output);
|
||||||
|
static void Main(string[] args)
|
||||||
Using the same PrintDelegate delegate as before, we could do this:
|
{
|
||||||
|
void PrintInLower(string text)
|
||||||
delegate void PrintDelegate(string output);
|
{
|
||||||
|
Console.WriteLine(text.ToLower());
|
||||||
static void Main(string[] args)
|
}
|
||||||
|
void PrintInUpper(string text)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine(text.ToUpper());
|
||||||
void PrintInLower(string text)
|
}
|
||||||
|
PrintDelegate print = PrintInLower;
|
||||||
{
|
print += PrintInUpper;
|
||||||
|
print("AaaBbbCcc");
|
||||||
Console.WriteLine(text.ToLower());
|
}
|
||||||
|
```
|
||||||
}
|
|
||||||
|
</div>
|
||||||
void PrintInUpper(string text)
|
<div markdown='1'>
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(text.ToUpper());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintDelegate print = PrintInLower;
|
|
||||||
|
|
||||||
print += PrintInUpper;
|
|
||||||
|
|
||||||
print("AaaBbbCcc");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Using Multicast Delegates (continued)
|
</div>
|
||||||
|
</div>
|
||||||
Methods can be removed from the delegate with the "-" operator:
|
|
||||||
|
### Removing methods
|
||||||
delegate void PrintDelegate(string output);
|
|
||||||
|
* Methods can be removed from the delegate with the `-` operator:
|
||||||
static void Main(string[] args)
|
```csharp
|
||||||
|
delegate void PrintDelegate(string output);
|
||||||
{
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
...
|
...
|
||||||
|
print -= PrintInLower;
|
||||||
print -= PrintInLower;
|
print("AaaBbbCcc"); // Outputs "AAABBBCCC"
|
||||||
|
...
|
||||||
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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Delegates - 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:
|
|
||||||
|
|
||||||
public class DelegateTest
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
public DelegateTest(PrintDelegate printDelegate)
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
printDelegate("This Method Was Called From Another Class!");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# Delegates - Example (continued)
|
|
||||||
|
|
||||||
Now we can create a new DelegateTest object and pass the delegate to the object constructor:
|
<div class='columns21' markdown='1'>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
```csharp
|
||||||
void PrintInLower(string text)
|
void PrintInLower(string text)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
Console.WriteLine(text.ToLower());
|
||||||
Console.WriteLine(text.ToLower());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintInUpper(string text)
|
void PrintInUpper(string text)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
Console.WriteLine(text.ToUpper());
|
||||||
Console.WriteLine(text.ToUpper());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize new delegate which is composed of PrintInLower method
|
// Initialize new delegate which is composed of PrintInLower method
|
||||||
|
|
||||||
PrintDelegate print = PrintInLower;
|
PrintDelegate print = PrintInLower;
|
||||||
|
|
||||||
// Add PrintInUpper method to the delegate
|
// Add PrintInUpper method to the delegate
|
||||||
|
|
||||||
print += PrintInUpper;
|
print += PrintInUpper;
|
||||||
|
|
||||||
// Send the delegate to the class constructor
|
// Send the delegate to the class constructor
|
||||||
|
|
||||||
DelegateTest delegateTest = new DelegateTest(print);
|
DelegateTest delegateTest = new DelegateTest(print);
|
||||||
|
```
|
||||||
|
|
||||||

|
</div>
|
||||||
|
<div markdown='1'>
|
||||||
# Anonymous Methods
|
|
||||||
|
|
||||||
Delegates can be initialized anonymously (without a specified name)
|
|
||||||
|
|
||||||
Anonymous method in variable declaration:
|
|
||||||
|
|
||||||
delegate void PrintDelegate(string output);
|
|
||||||
|
|
||||||
bool printUpper = true;
|
|
||||||
|
|
||||||
PrintDelegate printCheckUpper =
|
|
||||||
|
|
||||||
delegate (string text)
|
Now we can create a new DelegateTest object and pass the delegate to the object constructor:
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
if (printUpper)
|
|
||||||
|
|
||||||
Console.WriteLine(text.ToUpper());
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
Console.WriteLine(text);
|

|
||||||
|
|
||||||
};
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
printCheckUpper("I'm not angry!"); // Outputs I'M NOT ANGRY!
|
|
||||||
|
|
||||||
Notice that the actual method that prints the text is not declared anywhere
|
### Anonymous methods
|
||||||
|
|
||||||
You can use an empty anonymous method to initialize a delegate which does nothing:
|
* 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);
|
||||||
|
};
|
||||||
|
|
||||||
delegate void SomeDelegate();
|
printCheckUpper("I'm not angry!"); // Outputs I'M NOT ANGRY!
|
||||||
|
```
|
||||||
|
* Notice that the actual method that prints the text is not declared anywhere!
|
||||||
|
|
||||||
class Program
|
---
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
<div class='columns' markdown='1'>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
```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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
* 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!
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
<div class='columns' markdown='1'>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class Publisher
|
||||||
{
|
{
|
||||||
|
public event EventHandler SampleEvent;
|
||||||
|
protected virtual void OnSampleEvent()
|
||||||
|
{
|
||||||
|
SampleEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
static void Main(string[] args)
|
</div>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
class Program
|
||||||
{
|
{
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
var pub = new Publisher();
|
||||||
|
|
||||||
// Initialize an empty delegate, add method later...
|
pub.OnSampleEvent(); // ✅ Works fine
|
||||||
|
// pub.SampleEvent(); // ❌ Not allowed!
|
||||||
SomeDelegate myDelegate = new SomeDelegate(delegate { });
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
# Events
|
|
||||||
|
|
||||||
* Consider the following Game class:
|
|
||||||
* class Game
|
|
||||||
* {
|
|
||||||
* Sound gameOverSound;
|
|
||||||
* Window gameOverScreen;
|
|
||||||
* // Some game logic here...
|
|
||||||
* // ...
|
|
||||||
* void OnGameOver()
|
|
||||||
* {
|
|
||||||
* gameOverSound.Play(); // This plays some sound
|
|
||||||
* gameOverScreen.Show(); // This shows some screen
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* This raises a couple of problems:
|
|
||||||
* The Game class has to know about the Sound and Window classes
|
|
||||||
* Changes in either of the classes could break the code
|
|
||||||
* = Game is dependent of Sound and Window classes
|
|
||||||
---
|
|
||||||
|
|
||||||
Tight coupling
|
|
||||||
|
|
||||||
# Events (continued)
|
### Adding subscribers to the event
|
||||||
|
|
||||||
Events are signals that are raised by a __Publisher __ and received by a __Subscriber__
|
* 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
|
||||||
|
|
||||||
The publisher does not know or care what, if any, object receives the signal
|
<div class='columns' markdown='1'>
|
||||||
|
<div markdown='1'>
|
||||||
Changes in the subscriber classes do not affect the publisher
|
|
||||||
|
|
||||||
# Events - Example
|
|
||||||
|
|
||||||
Events are created in two steps:
|
|
||||||
|
|
||||||
Declare a delegate
|
|
||||||
|
|
||||||
Declare a variable of the delegate with the event keyword
|
|
||||||
|
|
||||||
public delegate void GameOver();
|
|
||||||
|
|
||||||
class Game
|
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class Publisher
|
||||||
{
|
{
|
||||||
|
public event EventHandler SampleEvent;
|
||||||
public event GameOver GameOverEventHandler;
|
protected virtual void OnSampleEvent()
|
||||||
|
{
|
||||||
// Some game logic here...
|
SampleEvent?.Invoke(this);
|
||||||
|
}
|
||||||
// ...
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Exercise 1: A Rudimentary Event System
|
</div>
|
||||||
|
<div markdown='1'>
|
||||||
Create a console application for controlling a plant treatment system, that has three methods which print the following outputs:
|
|
||||||
|
|
||||||
| void ReleaseWater() | Releasing water... |
|
```csharp {7-8}
|
||||||
| :-: | :-: |
|
class Program
|
||||||
| void ReleaseFertilizer() | Releasing fertilizer... |
|
{
|
||||||
| void IncreaseTemperature() | Increasing temperature... |
|
static void Main()
|
||||||
|
{
|
||||||
|
var pub = new Publisher();
|
||||||
|
|
||||||
|
pub.SampleEvent +=
|
||||||
|
() => Console.WriteLine("Sample event!");
|
||||||
|
|
||||||
|
pub.OnSampleEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
<div class='columns' markdown='1'>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
```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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div markdown='1'>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Exercise 1: A rudimentary event system
|
||||||
|
<!--_class: "exercise invert" -->
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
Create a main loop where the user can switch each method on (= add to a delegate) by writing its name. All methods are off by default. (Hint: you can just use switch-case for defining which method should be added to the delegate)
|
---
|
||||||
|
|
||||||
If the user types run, all the methods that are on (= added to the delegate), will be executed.
|
<!--_class: "exercise invert" -->
|
||||||
|
|
||||||

|
* ***Hint:*** you can just use switch-case for defining which method should be added to the delegate
|
||||||
|
|
||||||
|
* Here's an example console input & output:
|
||||||
|

|
||||||
|
Loading…
Reference in New Issue