- Lab
- Core Tech

Guided: Delegates and Events in C#
This Code Lab is centered around understanding the concepts of delegates and events in programming with C#. Delegates serve as function pointers, enabling you to create dynamic code structures to facilitate communication between components. Events, a crucial aspect of C# programming, will also be a focal point. You'll understand how to implement and subscribe to events, allowing your code to respond to specific occurrences in a decoupled manner. In this lab, you will apply events to incorporate a validation mechanic to enhance an existing application.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the Guided: Delegates and Events in C# lab.
In C# programming, the usage of delegates and events represents a pivotal paradigm shift. Delegates act as a type-safe, object-oriented way to represent references to methods. Events are a language feature built on top of delegates, providing a mechanism for components to communicate when an action or state change occurs. These features allow developers to create agile and responsive code structures.
The ticket tracker application you will be working with has most of its behind-the-scenes and main application logic already implemented for you. However, you will be responsible for adding some user validation to allow an administrator to login in to the application. You will be coupling this with usage of events to notify whether admin login was successful.
You can also check the solution directory if you are stuck or want to check your implementations at any time.
Note: The files in the solution directory puts the solution code in the
solution
namespace for compilation purposes. Do not copy thisnamespace solution
line or it will cause errors when running the application. Only take into account the code within the class block.
You're invited to experiment and learn interactively. Make use of the Terminal to the right to execute the ticket tracker application by entering the command
dotnet run --no-restore
when you have completed the lab. -
Challenge
Delegates
Overview
Delegates are custom defined types that can hold references to method(s). If you are familiar with function pointers from C and C++, then you will be quite familiar with delegates. However, delegates are also considered type-safe as the compiler performs type checking during compilation to ensure that any methods referred to by the delegate contain the correct return type and parameters as defined by the delegate.
Declaration and Instantiation
A delegate is typically declared using the
delegate
syntax:public delegate void ExampleDelegate(); public delegate int Calculate(int a, int b);
As seen in the example, they are declared just as you would with normal methods but with the
delegate
keyword preceding the return type. Once a delegate has been declared, any method that has the same return and parameters can be used to instantiate a delegate. When a delegate is instantiated with a method that it references, it is known as a delegate object.// Delegate declarations public delegate void ExampleDelegate(); public delegate int Calculate(int a, int b); static void ExampleMethod() { // Delegates can be both static or instance methods // Do some stuff } int Add(int x, int y) { // add x and y } int WrongArgNum(int g, int z, int t) { // Do something } int WrongArgType(int k, string m) { // Do Something } // Instantiate a delegate object like you would with any variable ExampleDelegate example1 = ExampleMethod; Calculate example2 = Add; Calculate fail1 = ExampleMethod; // Won't work, Calculate delegate requires int return type but ExampleMethod is void return type. Calculate fail2 = WrongArgNum; // Won't work, Calculate specifies 2 parameters of type int but WrongArgNum has 3 Calculate fail3 = WrongArgType; // Won't work, Calculate requires that both its parameters be of type int example1(); // Performs ExampleMethod example2(2, 2); // Performs Add(2, 2) // Can be instantiated using anonymous method ExampleDelegate anonFunc = delegate() { Console.WriteLine("anonymous delegate!"); }; // Can be instantiated using lambda function ExampleDelegate lambdaFunc = () => { Console.WriteLine("lambda delegate!"); };
Multicast Delegates and Usage
Delegate objects can be multicast when assigned multiple methods that match their signature. Delegate objects can also be combined with other delegate objects of the same type. When executed, they perform their methods in the order in which they were added.
// Declare delegate and method definitions public delegate void ExampleDelegate(); public static void p1() { Console.Write("a"); } public void p2() { Console.Write("b"); } public void p3() { Console.Write("c"); } // Instantiate delegate objects ExampleDelegate ex1 = p1 + p2; // ab ExampleDelegate ex2 = p2 + p3; // bc ex1 -= p2; // a ex1 += p3; // ac ex1 += ex2; // abc ex1 += p2 // abcb ex1 -= p1; // bcb
Most importantly, delegate objects can be used as parameters to be passed into other methods. This essentially lets you pass in delegates as callback functions for other methods.
public delegate void SortDelegate(List<Book> books); void SortByName(List<Book> books) { // Sort books by name } void SortByAuthor(List<Book> books) { // Sort books by author } void SortByGenre(List<Book> books) { // Sort books by genre } void SortBooks(SortDelegate sortingMethod) { // SortDelegate type for parameter // Sort books using specified method } // Instantiate delegate objects SortDelegate s1 = SortByName; SortDelegate s2 = SortByAuthor; SortDelegate s3 = SortByGenre; SortBooks(s1); SortBooks(s2); SortBooks(s3); // SortBooks can sort by any of the specified sorting methods as they are all attached to SortDelegate objects.
-
Challenge
Events
Overview
Events in C# are essentially messages sent by a class that informs other classes that something has occurred. The class that sends this event, or raises it, is known as the publisher. The class(es) that receive this event, or handles it, are known as the subscribers.
Events are related to delegates because they are built on top of delegates. For instance, an event handler declaration would typically go as follows:
public delegate void MyEventHandler(object? sender, CustomEventArgs e)
There are a few things to note here. First is that event handlers must always be of
void
return type. Secondly, the parameters here specifyobject? sender
andCustomEventArgs e
. Thesender
ostensibly refers to whatever object raised the event (with the?
being necessary since C# 8 telling the compiler that there is a possibility that the event isnull
). TheCustomEventArgs
represents the arguments provided by the event.
EventHandler
While the example above specifies a custom event handler, it is considered best practice in C# to utilize the predefined
EventHandler
andEventHandler<TEventArgs>
patterns where possible. You should only really use a custom event handler pattern if neitherEventHandler
andEventHandler<TEventArgs>
is able to meet your needs. In fact, the documentation shows the definition forEventHandler
is simplypublic delegate void EventHandler(object? sender, EventArgs e);
Now what is the difference between
EventHandler
andEventHandler<TEventArgs>
? Simply put,EventHandler
is for when you are not sending custom or important data in the event to the handler. So if you're handler does not need any information besides the fact that the event has occurred, thenEventHandler
is the way to go. However, if you're handler needs any kind of information included with the event, then you want to use theEventHandler<TEventArgs>
generic.Remember the
EventArgs
from theEventHandler
definition? That's the base class for event arguments. If you remember theCustomEventArgs
parameter from the overview, that is a derived class that you would need to manually implement fromEventArgs
if you want to send event data from the publisher to the subscriber. You also need to do this if you want to define your own custom event handler instead of using either of the predefined patterns.Usage
Now let's take a look at how you would see event usage:
// Custom event class for usage in the generic EventHandler<TEventArgs> public class ExampleEventArgs : EventArgs { public string data { get; set; } public ExampleEventArgs(string msg) { data = msg; } } class EventSender { // Publisher class // Declare example events public event EventHandler simpleEvent; public event EventHandler<ExampleEventArgs> genericEvent; // This method is ONLY for genericEvent. It is used to raise the event when sending data to subscribers // Best practice to be set as protected virtual to allow derived classes the ability to override or extend the behavior of raising the event // Another best practice is the name the event raising method OnEventName protected virtual void OnExampleEvent(ExampleEventArgs e) { // Do any necessary things in here genericEvent?.Invoke(this, e); } // Place your event raisers in here. When called, the subscribers will be notified public void ExampleMethod() { // Code that does stuff // Used when using the non-generic. Always use EventArgs.Empty since you are not providing extra information simpleEvent?.Invoke(this, EventArgs.Empty); OnExampleEvent(new ExampleEventArgs("generic event occurred!")); } } class EventReceiver { // Handles raised event public void HandleSimpleEvent(object? sender, EventArgs e) { Console.WriteLine("Simple event handled!"); } public void HandleGenericEvent(object? sender, ExampleEventArgs e) { Console.WriteLine($"{e.data}"); } } // Main EventSender sender = new EventSender(); EventReceiver handler = new EventReceiver(); sender.simpleEvent += handler.HandleSimpleEvent; //Attach handler to event, aka subscribe to publisher sender.genericEvent += handler.HandleGenericEvent; //Attach generic handler to event sender.ExampleMethod(); //Will trigger the event in the publisher //Since event is triggered, subscriber will perform HandleSimpleEvent and HandleGenericEvent }
This may seem like a lot to digest, but take a look at it a couple times and it should start to make sense. To put it as simply as possible, this is what is happening:
- Define an event in the publisher class.
- Invoke the event in one of the publisher classes methods.
- Define a handler method in the subscriber class.
- Instantiate both classes, and attach the subscribers handler method to the publishers event using something like
+=
. - Whenever the publisher calls the method that invokes the event, the subscribers handler method will execute.
-
Challenge
The Application
Overview
Now let's take a look at the application you will be working on. Currently the ticket tracker application only allows users to add a bus or train ticket to the system. There is a login option, but it doesn't really do anything.
You will be responsible for adding some simple validation that will allow users to login as an admin. Once logged in, they will be given the ability to view all tickets in the system and to remove tickets from the system. You will be utilizing events (and by extension, delegates) to notify users whether login was a success. For this scenario, you will be utilizing the
EventHandler<TEventArgs>
generic along with implementing a simple subclass forEventArgs
similar to what was demonstrated in the previous step.As mentioned previously, feel free to checkout the solution directory for help or to compare your implementations. Remember that the solution file is not the only solution, so it's alright to have some divergence so long as your application is functioning as expected. Again, be sure to actively run the application to see what the compiler is saying. It's something that you should be doing by second nature moving forward!
TicketManager
First, you will need to complete
TicketManager.cs
, which will also be the publisher class.At the top, add a new class that will represent your custom event argument.
Instructions
1. Create a `public` class with a name of or similar to `LoginEventArgs`. 2. Make sure this class implements `EventArgs` using `:`. 3. This class should have a single `bool` property named `IsSuccessful` or something similar.Within the
TicketManager
class, define an event using theEventHandler<TEventArgs>
genericInstructions
1. Create a `public event` field of type `EventHandler?`. Replace `TEventArgs` with the custom argument class you defined above. 2. Name this field `LoginEvent` or something similar. With the event defined, you will now need to define a method to raise it.
Instructions
1. Create a method called called `OnLogin`. It should take your custom event argument type as a parameter. 2. Mark this method as `protected virtual` with a `void` return type. 3. Invoke the event using `LoginEvent?.Invoke(this, e)` or similar.Lastly, you will need to implement the
Login
function. The signature has been defined but it has no method body.Instructions
1. Create a `adminUsername` and `adminPassword` string. It can be whatever you wish. 2. Create a `bool` such as `loginSuccessful`. It should only be true when the `username` and `password` parameters both match your specified username and password for admin. 3. If `loginSuccessful` is true, set the `IsLoggedIn` property to true. 4. Raise an event using `OnLogin`. Pass in a new instance of your custom event argument, such as `new LoginEventArgs { IsSuccessful = loginSuccessful }`.With that,
TicketManager.cs
should be complete.
Program
Finally, you will need to create your event handler within
Program.cs
which subscribes to theLoginEvent
.Instructions
1. Create a method called `HandleLoginEvent()`. It should be `private static void`. 2. It should have the `object? sender` and your custom even argument as parameters. 3. Check if `IsSuccessful` from your custom event argument is true. If it is, print out a success message using `Console.WriteLine()`. Otherwise, print out a failure message. 4. Look for the line `TicketManager ticketManager = new TicketManager()` near the top of the `Main()` method. 5. Immediately after, set your `LoginEvent` in `ticketManager` to your newly defined event handler, for example `ticketManager.LoginEvent += HandleLoginEvent`.
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.