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);
static void Main(string[] args)
{
...
print -= PrintInLower;
print("AaaBbbCcc"); // Outputs "AAABBBCCC"
...
}
</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!");
}
}
```
# 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;
PrintDelegate printCheckUpper =
</div>
<div markdown='1'>
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
</div>
</div>
# 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
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
<div class='columns' markdown='1'>
<div markdown='1'>
```csharp
public class Publisher
{
public event GameOver GameOverEventHandler;
// Some game logic here...
// ...
public event EventHandler SampleEvent;
protected virtual void OnSampleEvent()
{
SampleEvent?.Invoke(this);
}
}
```
# 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:
</div>
<div markdown='1'>
| void ReleaseWater() | Releasing water... |
| :-: | :-: |
| void ReleaseFertilizer() | Releasing fertilizer... |
| void IncreaseTemperature() | Increasing temperature... |
```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
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