Cookies   I display ads to cover the expenses. See the privacy policy for more information. You can keep or reject the ads.

Video thumbnail
Hi everyone, in this episode we're going to look at a way of using delegates that is conceptually
very different from how we used them last episode.
Let's consider a simple scenario where we have a game with a Player class, an Achievements
class, and a UserInterface class.
The Player class has a "Die" method, which we can imagine gets called when the player's
health falls below zero.
Now let's say that the Achievements class wants to know when the player dies, so that
it can keep count for the 'Die 1000 times' achievement or whatever.
And the UI class also wants to know when the player dies, so that it can show the gameover
screen.
To this end, we can add an OnPlayerDeath method to both of these classes.
The question is, how do we actually call these methods when the player dies?
The simplest solution is to make sure that the OnPlayerDeath methods are public, and
then, in the "Die" method of the Player class, to get references to the Achievements and
UI classes, and simply call OnPlayerDeath on those references.
This is a tempting way to do it, because it's so quick and easy, but there are some pretty
good arguments against it.
Mainly, it's simply not the Player class's job to keep track of who wants to know when
the player has died.
And what if some other classes also want to know when the Player fires a weapon, or kills
an enemy, or opens a treasure chest.
The Player class could quickly become littered with references to other classes, obscuring
the actual functionality of the Player code.
Furthermore, since the Player class now references these other classes, it is dependent on their
existence.
We couldn't remove any of these classes from the project, without breaking the Player class.
This makes reusing code very annoying: you want to reuse your Player code in another
project, but first you have to weed out all the references to different classes that don't
exist in the new project.
Ideally what we want is for the Player class, instead of individually informing other classes
when the player dies, to simply announce when it happens.
It is then the responsibility of any class that wants to know when the player dies, to
actually listen out for that announcement.
So I'll remove the references I created in the Player class, and instead create a public
delegate up at the top of the class, which is void and has no parameters — just like
the OnPlayerDeath methods.
I'll call it the DeathDelegate.
We can then make an instance of this delegate - so public DeathDelegate deathEvent, and
when the player dies, we will invoke this delegate.
It's important, before invoking a delegate, to make sure that it is not null.
This is to prevent an error from occurring in the case that nothing has been assigned
to the delegate.
In the Achievements class, let's make a Start method, which we can imagine will be called
at the beginning of the game.
Here we can get a reference to the Player class, and subscribe the OnPlayerDeath method
to the deathEvent.
Note the "plus equals" notation for subscribing a method to a delegate.
When the player dies, it would be good practice to unsubscribe from the delegate, using the
"minus equals" operator.
Let's copy this over to the UI class as well.
So now, when the player dies, the deathEvent is invoked, and all the methods subscribed
to it will be called.
This is fantastic, but there are two things that can go wrong.
The first is if we accidentally write "=" instead of "+=" when subscribing a method to the delegate.
This will overwrite anyone else that has already subscribed to that delegate, and so they won't
be notified.
The second is that, any class with a reference to the Player, can invoke the delegate by
simply writing player.deathEvent(); Everyone who has subscribed to the delegate will now
mistakenly think that the player is dead.
Of course neither of these things issue an issue as long as one is careful when programming.
However, the last thing we need as programmers, is to give ourselves more opportunity to screw up.
This is where events come in.
To make a delegate into an event, one simply adds the "event" keyword when creating the
delegate instance.
All of a sudden, classes other than the one in which the delegate is defined, are no longer
able to assign directly to the delegate - they can only subscribe and unsubscribe
using "+=" and "-="
Furthermore, they are not able to invoke the delegate.
So you can think of the event keyword as simply adding these two restrictions to the delegate.
I'd like to mention two more things: actions and funcs.
These don't provide any new functionality, they are simply convenient shortcuts for creating
delegate variables.
They're both part of the System namespace, so you'll need to write 'using System;' to
access them.
So the idea is that, in order to create this deathEvent variable, we had to first create
the DeathDelegate to specify the return type and parameters.
Actions and Funcs allow us to skip this step, and go straight to creating the variable.
So I could replace these two lines with public event Action deathEvent; This is because "Action"
represents a void delegate with no parameters.
The generic versions of Action, can be used to create a void delegate with a single, or
multiple parameters.
Func can be used to create a delegate with a custom return type, and no parameters.
Func can also be used to create a delegate with a custom return type, as well as parameters.
This is done by specifying multiple types when creating the Func.
The last type specified will be the return type, and all previous ones will be parameters.
For example, creating a Func of type int, string bool; is equivalent to making a delegate
that returns a bool, and takes in an int and string parameter.
And, then making an instance of that delegate.
I hope you’ve found all this to be helpful.
If you’d like to support these videos, you can do so on Patreon.
Cheers.