finish lecture 11, fix theme not being applied in cli html conversion

- update readme
main
borb 2 weeks ago
parent 0f0335833e
commit 88c8c953c6

@ -5,7 +5,7 @@
{
"match": "\\.md$",
"notMatch": "README\\.md$",
"cmd": "marp \"${fileDirname}\\${fileBasename}\" -o \"${fileDirname}\\${fileBasenameNoExt}-slides.html\" --html true",
"cmd": "marp \"${fileDirname}\\${fileBasename}\" -o \"${fileDirname}\\${fileBasenameNoExt}-slides.html\" --html true --theme \"${fileDirname}\\.themes\\buutti.css\"",
"useShortcut": false,
"silent": false
},

File diff suppressed because one or more lines are too long

@ -1,289 +1,448 @@
# Delegates and Events
![](imgs/11%20Delegates%20and%20Events_0.png)
---
marp: true
paginate: true
math: mathjax
theme: buutti
title: N. Delegates and events
---
# Overview
Delegates
# Delegates and events
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
* 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:
delegate \<return type> \<delegate name>(\<parameters>);
Example:
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:
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:
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");
}
### 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:
<div class='columns21' markdown='1'>
<div markdown='1'>
```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");
}
```
</div>
<div markdown='1'>
![](imgs/11%20Delegates%20and%20Events_1.png)
# Using Multicast Delegates (continued)
Methods can be removed from the delegate with the "-" operator:
delegate void PrintDelegate(string output);
</div>
</div>
### 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!");
}
}
```
static void Main(string[] args)
{
...
print -= PrintInLower;
print("AaaBbbCcc"); // Outputs "AAABBBCCC"
...
}
# 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)
{
Console.WriteLine(text.ToLower());
Console.WriteLine(text.ToLower());
}
void PrintInUpper(string text)
{
Console.WriteLine(text.ToUpper());
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);
```
![](imgs/11%20Delegates%20and%20Events_2.png)
# Anonymous Methods
Delegates can be initialized anonymously (without a specified name)
Anonymous method in variable declaration:
delegate void PrintDelegate(string output);
bool printUpper = true;
</div>
<div markdown='1'>
PrintDelegate printCheckUpper =
delegate (string text)
{
if (printUpper)
Console.WriteLine(text.ToUpper());
else
Now we can create a new DelegateTest object and pass the delegate to the object constructor:
Console.WriteLine(text);
![](imgs/11%20Delegates%20and%20Events_2.png)
};
</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...
SomeDelegate myDelegate = new SomeDelegate(delegate { });
}
pub.OnSampleEvent(); // ✅ Works fine
// pub.SampleEvent(); // ❌ Not allowed!
}
}
```
# 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)
Events are signals that are raised by a __Publisher __ and received by a __Subscriber__
The publisher does not know or care what, if any, object receives the signal
Changes in the subscriber classes do not affect the publisher
# Events - Example
Events are created in two steps:
</div>
</div>
Declare a delegate
### Adding subscribers to the event
Declare a variable of the delegate with the event keyword
* 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
public delegate void GameOver();
class Game
<div class='columns' markdown='1'>
<div markdown='1'>
```csharp
public class Publisher
{
public event EventHandler SampleEvent;
protected virtual void OnSampleEvent()
{
SampleEvent?.Invoke(this);
}
}
```
public event GameOver GameOverEventHandler;
</div>
<div markdown='1'>
// Some game logic here...
```csharp {7-8}
class Program
{
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
# Exercise 1: A Rudimentary Event System
Create a console application for controlling a plant treatment system, that has three methods which print the following outputs:
| void ReleaseWater() | Releasing water... |
| :-: | :-: |
| void ReleaseFertilizer() | Releasing fertilizer... |
| void IncreaseTemperature() | Increasing temperature... |
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" -->
![](imgs/11%20Delegates%20and%20Events_3.png)
* ***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)

@ -4,6 +4,7 @@ Material completion denoted with 🌑🌘🌗🌖🌕 .
| # | Lecture | Materials | Exercises |
|---:|------------------------------------------------------------------------|----------:|----------:|
| 11 | [Delegates and Events](11-delegates-and-events.md) | 🌕 | 🌗 |
| 12 | [Files and Streams](12-files-and-streams.md) | 🌕 | 🌕 |
| 13 | [Generics, IEnumberable and LINQ](13-generics-ienumerable-and-linq.md) | 🌕 | 🌕 |
| 14 | [Exceptions, Threads and Tasks](14-exceptions-threads-and-tasks.md) | 🌕 | 🌕 |

Loading…
Cancel
Save