Welcome back to a new C# tutorial. Lets take a look at C# Events and Delegates!
In this tutorial, we will learn about multicast delegates and events!.
Let’s first explore the problem.
Imagine we are developing a video game from scratch; our code is a combination of Graphics and Audio libraries and the logic for our player.
So let’s start coding it : ).
Setting up The Project
Consider the following simple classes.
1. RenderingEngine Class
//RenderingEngine Class
public class RenderingEngine {
//at the start of the game, we want to enable the rendering engine and start drawing the visuals
public void StartGame() {
Console.WriteLine("Rendering Engine Started");
Console.WriteLine("Drawing Visuals....");
}
//when the game is over, we want to stop our rendering engine
public void GameOver() {
Console.WriteLine("Rendering Engine Stopped");
}
}
2. AudioSystem Class
//Audio System Class
public class AudioSystem {
//at the start of the game, we want to enable the audio system and start playing audio clips
public void StartGame() {
Console.WriteLine("Audio System Started...");
Console.WriteLine("Playing Audio...");
}
//when the game is over, we want to stop the audio system
public void GameOver() {
Console.WriteLine("Audio System Stopped");
}
}
3. Player Class
//Player class
public class Player {
//Player name
public string PlayerName { get; set; }
//simple constructor
public Player(string name) {
this.PlayerName = name;
}
//at the start of the game, spawn the player.
public void StartGame() {
Console.WriteLine($"Spawning Player with ID : {PlayerName}");
}
//when the game is over, remove the player from the game
public void GameOver() {
Console.WriteLine($"Removing Player with ID : {PlayerName}");
}
}
So now, in the main method, let’s create an audio system object, a rendering engine object, and a couple of players.
static void Main(string[] args) {
//create an audio system
AudioSystem audioSystem = new AudioSystem();
//create a rendering engine
RenderingEngine renderingEngine = new RenderingEngine();
//create two players and give them Id's
Player player1 = new Player("SteelCow");
Player player2 = new Player("DoggoSilva");
//pause
Console.Read();
}
We need to start our rendering engine, audio system and spawn the players by calling the StartGame method from each class.
static void Main(string[] args) {
//create an audio system
AudioSystem audioSystem = new AudioSystem();
//create a rendering engine
RenderingEngine renderingEngine = new RenderingEngine();
//create two players and give them Id's
Player player1 = new Player("SteelCow");
Player player2 = new Player("DoggoSilva");
//start the audio system and the rendering engine
audioSystem.StartGame();
renderingEngine.StartGame();
//spawn the players
player1.StartGame();
player2.StartGame();
Console.WriteLine("Game is Running....Press any key to end the game.");
Console.Read();
}
Also, we need to shut them down and remove the players at the end of our program by calling GameOver from each class as well!.
static void Main(string[] args) {
//create an audio system
AudioSystem audioSystem = new AudioSystem();
//create a rendering engine
RenderingEngine renderingEngine = new RenderingEngine();
//create two players and give them Id's
Player player1 = new Player("SteelCow");
Player player2 = new Player("DoggoSilva");
//start the audio system and the rendering engine
audioSystem.StartGame();
renderingEngine.StartGame();
//spawn the players
player1.StartGame();
player2.StartGame();
Console.WriteLine("Game is Running....Press any key to end the game.");
//pause
Console.Read();
//shut down the rendering engine and the audio system
renderingEngine.GameOver();
audioSystem.GameOver();
//remove the players
player1.GameOver();
player2.GameOver();
//pause
Console.Read();
}
—Run the app—
So our application is working correctly but, what if we decided to add more systems or more players?.
We can easily forget to call the StartGame or GameOver, or even worse, call one of the methods twice, which could cause all kinds of problems and require us to place a lot of protection code!.
This is where multicast delegates come in handy : ). A multicast delegate is a delegate that works as a list where it can store a reference to more than one method; it’s as simple as that !.
So let’s create a static class called GameEventManager which will handle our multicast delegates.
public static class GameEventManager {
}
And let’s define a new delegate type called GameEvent which will take nothing for its parameters and return nothing.
public static class GameEventManager {
//a new delegate type called GameEvent
public delegate void GameEvent();
}
Now let’s create two delegates variables called OnGameStart and OnGameOver, which are the two main events happening on our objects.
public static class GameEventManager {
//a new delegate type called GameEvent
public delegate void GameEvent();
//create two delegates variables called OnGameStart and OnGameOver
public static GameEvent OnGameStart, OnGameOver;
}
Subscribing to our Delegates
Back to our 3 Classes, let’s add the StartGame and GameOver methods to the OnGameStart and OnGameOver delegates.
Also, there is no need for these methods to be public so let’s make them private.
The best place to add our methods to the GameEventManager delegates is from within the constructor itself. When an object gets created, its methods will automatically be added to the delegates or, in other words, Subscribe to them.
1. Audio System Class
As we said since a delegate can actually store more than one method at the same time, making them multicast delegates we will use += to add a method to the delegate, not just =.
//Audio System Class
public class AudioSystem {
//simple constructor
public AudioSystem() {
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game we want to enable the audio system and start playing audio clips
private void StartGame() {
Console.WriteLine("Audio System Started...");
Console.WriteLine("Playing Audio...");
}
//when the game is over we want to stop the audio system
private void GameOver() {
Console.WriteLine("Audio System Stopped");
}
}
2. RenderingEngine Class
//RenderingEngine Class
public class RenderingEngine {
//simple constructor
public RenderingEngine() {
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game we want to enable the rendering engine and start drawing the visuals
private void StartGame() {
Console.WriteLine("Rendering Engine Started");
Console.WriteLine("Drawing Visuals....");
}
//when the game is over we want to stop our rendering engine
private void GameOver() {
Console.WriteLine("Rendering Engine Stopped");
}
}
3. Player Class
//Player class
public class Player {
//Player name
public string PlayerName { get; set; }
//simple constructor
public Player(string name) {
this.PlayerName = name;
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game spawn the player.
private void StartGame() {
Console.WriteLine($"Spawning Player with ID : {PlayerName}");
}
//when the game is over remove the player from the game
private void GameOver() {
Console.WriteLine($"Removing Player with ID : {PlayerName}");
}
}
Now in our GameEventManager, Let’s add two public static methods to trigger the events from the main methods.
//a static class to handle our game events
public static class GameEventManager {
//a new delegate type called GameEvent
public delegate void GameEvent();
//create two delegates variables called OnGameStart and OnGameOver
public static event GameEvent OnGameStart, OnGameOver;
//a static method to trigger OnGameStart
public static void TriggerGameStart() {
//check if the OnGameStart event is not empty, meaning that other methods already subscribed to it
if (OnGameStart != null) {
//print a simple message
Console.WriteLine("The game has started....");
//call the OnGameStart that will trigger all the methods subscribed to this event
OnGameStart();
}
}
//a static method to trigger OnGameOver
public static void TriggerGameOver() {
//check if the OnGameOver event is not empty, meaning that other methods already subscribed to it
if (OnGameOver != null) {
//print a simple message
Console.WriteLine("The game is over...");
//call the OnGameOver that will trigger all the methods subscribed to this event
OnGameOver();
}
}
}
Now let’s have a look at the code in our main method. We will see a lot of errors, this is because our StartGame and GameOver methods are no longer public. But it’s fine since we no longer need to call them directly after we made sure they are subscribed to the events in our GameEventManager class.
static void Main(string[] args) {
//create an audio system
AudioSystem audioSystem = new AudioSystem();
//create a rendering engine
RenderingEngine renderingEngine = new RenderingEngine();
//create two players and give them Id's
Player player1 = new Player("SteelCow");
Player player2 = new Player("DoggoSilva");
//trigger the GameStart event
GameEventManager.TriggerGameStart();
Console.WriteLine("Game is Running....Press any key to end the game.");
//pause
Console.Read();
//trigger the GameOver event
GameEventManager.TriggerGameOver();
//pause
Console.Read();
}
}
Now let’s add a new player for example and run the application.
static void Main(string[] args) {
//create an audio system
AudioSystem audioSystem = new AudioSystem();
//create a rendering engine
RenderingEngine renderingEngine = new RenderingEngine();
//create two players and give them Id's
Player player1 = new Player("SteelCow");
Player player2 = new Player("DoggoSilva");
Player player3 = new Player("DragonDog");
//trigger the GameStart event
GameEventManager.TriggerGameStart();
Console.WriteLine("Game is Running....Press any key to end the game.");
//pause
Console.Read();
//trigger the GameOver event
GameEventManager.TriggerGameOver();
//pause
Console.Read();
}
}
We can see that our third player got his StartGame and GameOver methods called automatically. Because our objects were notified when both the OnGameStart and OnGameOver events got triggered : )
The Problem with Delegates
Now when using delegates some things can go wrong.
Let’s first see some of the mistakes that can cost us hours to fix.
Let’s say by mistake we instead of subscribing a method to our events using +=, we used =.
In the Player Class:
Change the += to =.
//Player class
public class Player {
//Player name
public string PlayerName { get; set; }
//simple constructor
public Player(string name) {
this.PlayerName = name;
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart = StartGame;
GameEventManager.OnGameOver = GameOver;
}
//at the start of the game spawn the player.
private void StartGame() {
Console.WriteLine($"Spawning Player with ID : {PlayerName}");
}
//when the game is over remove the player from the game
private void GameOver() {
Console.WriteLine($"Removing Player with ID : {PlayerName}");
}
}
This way we will remove all the methods referenced in the OnGameStart and OnGameOver events, and setting the last Player object that was created as the only subscriber to these events!.
Also since the OnGameStart and OnGameOver events are actually public we can call them directly from anywhere.
In the RenderingEngine Class:
Let’s call the OnGameStart directly from within the constructor.
//RenderingEngine Class
public class RenderingEngine {
//simple constructor
public RenderingEngine() {
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
GameEventManager.OnGameStart();
}
//at the start of the game we want to enable the rendering engine and start drawing the visuals
private void StartGame() {
Console.WriteLine("Rendering Engine Started");
Console.WriteLine("Drawing Visuals....");
}
//when the game is over we want to stop our rendering engine
private void GameOver() {
Console.WriteLine("Rendering Engine Stopped");
}
}
Now let’s run the application.
It’s all messed up!. Now we can’t mark our events as private because we need them to be public to subscribe to them in our classes constructors.
C# Events
Now we’ve been using the name event when mentioning delegates because our delegates were literally behaving like events!. But in fact, there is a special type of delegates called events that is made to prevent these kinds of mistakes.
In The GameEventManager Class:
Mark our delegates as events using the event keyword.
//a static class to handle our game events
public static class GameEventManager {
//a new delegate type called GameEvent
public delegate void GameEvent();
//create two delegates variables called OnGameStart and OnGameOver
public static event GameEvent OnGameStart, OnGameOver;
//a static method to trigger OnGameStart
public static void TriggerGameStart() {
//check if the OnGameStart event is not empty, meaning that other methods already subscribed to it
if (OnGameStart != null) {
//print a simple message
Console.WriteLine("The game has started....");
//call the OnGameStart that will trigger
OnGameStart();
}
}
//a static method to trigger OnGameOver
public static void TriggerGameOver() {
//check if the OnGameStart event is not empty, meaning that other methods already subscribed to it
if (OnGameOver != null) {
//print a simple message
Console.WriteLine("The game is over...");
//call the OnGameStart that will trigger
OnGameOver();
}
}
}
You will notice that we have few errors that popped suddenly.
Error 1
In the RenderingEngine Constructor:
We can no longer call the event directly since OnGameStart is now an event, we will be only allowed to call it from within the class it was created in.
—Remove the line with the error—
//RenderingEngine Class
public class RenderingEngine {
//simple constructor
public RenderingEngine() {
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game we want to enable the rendering engine and start drawing the visuals
private void StartGame() {
Console.WriteLine("Rendering Engine Started");
Console.WriteLine("Drawing Visuals....");
}
//when the game is over we want to stop our rendering engine
private void GameOver() {
Console.WriteLine("Rendering Engine Stopped");
}
}
Error 2
In the Player class constructor.
When a delegate is marked as an event, it is forced to behave like a list, so having an event on the left side of the = is no longer allowed!
—Change the = back to += —
//Player class
public class Player {
//Player name
public string PlayerName { get; set; }
//simple constructor
public Player(string name) {
this.PlayerName = name;
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game spawn the player.
private void StartGame() {
Console.WriteLine($"Spawning Player with ID : {PlayerName}");
}
//when the game is over remove the player from the game
private void GameOver() {
Console.WriteLine($"Removing Player with ID : {PlayerName}");
}
}
Finally, run the app again!
Perfect!
If you want to read more please check out this article C# OnClick Events.
Complete Code for C# Events and Delegates
using System;
using System.Collections.Generic;
namespace DelegatesTestingGround {
class Program {
static void Main(string[] args) {
//create an audio system
AudioSystem audioSystem = new AudioSystem();
//create a rendering engine
RenderingEngine renderingEngine = new RenderingEngine();
//create two players and give them Id's
Player player1 = new Player("SteelCow");
Player player2 = new Player("DoggoSilva");
Player player3 = new Player("DragonDog");
//trigger the GameStart event
GameEventManager.TriggerGameStart();
Console.WriteLine("Game is Running....Press any key to end the game.");
//pause
Console.Read();
//trigger the GameOver event
GameEventManager.TriggerGameOver();
//pause
Console.Read();
}
}
//a static class to handle our game events
public static class GameEventManager {
//a new delegate type called GameEvent
public delegate void GameEvent();
//create two delegates variables called OnGameStart and OnGameOver
public static event GameEvent OnGameStart, OnGameOver;
//a static method to trigger OnGameStart
public static void TriggerGameStart() {
//check if the OnGameStart event is not empty, meaning that other methods already subscribed to it
if (OnGameStart != null) {
//print a simple message
Console.WriteLine("The game has started....");
//call the OnGameStart that will trigger
OnGameStart();
}
}
//a static method to trigger OnGameOver
public static void TriggerGameOver() {
//check if the OnGameStart event is not empty, meaning that other methods already subscribed to it
if (OnGameOver != null) {
//print a simple message
Console.WriteLine("The game is over...");
//call the OnGameStart that will trigger
OnGameOver();
}
}
}
//Audio System Class
public class AudioSystem {
//simple constructor
public AudioSystem() {
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game we want to enable the audio system and start playing audio clips
private void StartGame() {
Console.WriteLine("Audio System Started...");
Console.WriteLine("Playing Audio...");
}
//when the game is over we want to stop the audio system
private void GameOver() {
Console.WriteLine("Audio System Stopped");
}
}
//RenderingEngine Class
public class RenderingEngine {
//simple constructor
public RenderingEngine() {
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game we want to enable the rendering engine and start drawing the visuals
private void StartGame() {
Console.WriteLine("Rendering Engine Started");
Console.WriteLine("Drawing Visuals....");
}
//when the game is over we want to stop our rendering engine
private void GameOver() {
Console.WriteLine("Rendering Engine Stopped");
}
}
//Player class
public class Player {
//Player name
public string PlayerName { get; set; }
//simple constructor
public Player(string name) {
this.PlayerName = name;
//subscribe to the OnGameStart and OnGameOver events.
GameEventManager.OnGameStart += StartGame;
GameEventManager.OnGameOver += GameOver;
}
//at the start of the game spawn the player.
private void StartGame() {
Console.WriteLine($"Spawning Player with ID : {PlayerName}");
}
//when the game is over remove the player from the game
private void GameOver() {
Console.WriteLine($"Removing Player with ID : {PlayerName}");
}
}
}