Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
1h 9m 13s
Events in C# are a fundamental concept that many developers use but may not fully understand, particularly when it comes to creating their own events. Tim Corey provides a comprehensive guide on how to create and use events, exploring best practices and advanced features in his video "C# Events - Creating and Consuming Events in Your Application".
In this article, we are going to explore C# events, focusing on their syntax, how they are defined, and how they help solve common programming problems using Tim Corey's video examples. Understanding event handling in C# is essential for building responsive applications, and this article will help you grasp the core concepts of custom error logging, exception handling, and event-driven programming.
In C# and programming languages, events play a critical role in event-driven programming, enabling applications to respond to actions such as user interactions or system changes. Compared to other programming languages, C# offers a structured approach to handling events, making it ideal for both fast and small applications. Events in C# are triggered by specific actions, and these actions invoke corresponding event handlers to perform the desired tasks.
Tim begins by explaining that most developers are familiar with events in C# but may not know how to create custom events. The goal of the video is to introduce events, walk through creating custom events, discuss their features, and outline best practices.
Tim uses a simple banking application built with Windows Forms (WinForms) to demonstrate events. The application simulates basic banking operations for one customer, including viewing balances and recording transactions.
Application Overview:
Main Form:
Transaction Form:
Tim explains the backend code of the demo banking application:
Customer Class:
Account Class:
Handling Deposits and Payments:
Overdraft Protection:
For code samples, you can refer to Tim Corey's video directly where he explains the code from 3:18 to 18:27.
In this section, Tim Corey dived into the workings of button click events in Windows Forms, explaining how these events are wired up and how they function.
Tim begins by explaining the familiar concept of button click events in Windows Forms applications. When you double-click a button in the designer, Visual Studio automatically generates an event handler method for the click event.
Event Handler Method:
This method is called whenever the button is clicked. It is where you place the code that should run in response to the button click.
private void recordTransactionButton_Click(object sender, EventArgs e)
{
// Code to handle the button click event
}
Wiring Up the Event:
The event handler method is wired up to the button click event in the form's designer file (FormName.Designer.cs). This is done using the += operator to add the event handler to the button's click event.
this.recordTransactionButton.Click += new System.EventHandler(this.recordTransactionButton_Click);AddHandler Me.recordTransactionButton.Click, AddressOf Me.recordTransactionButton_ClickTim explains how to create custom events in C#, starting with the Account class where events will be triggered.
Defining an Event:
An event is defined using the event keyword. It looks similar to a public variable but is specifically for events.
public event EventHandler<string> RaiseTransactionApprovedEvent;Public Event RaiseTransactionApprovedEvent As EventHandler(Of String)Triggering the Event:
Events are triggered using the Invoke method, typically within the class that defines the event. The Invoke method takes two parameters: the sender (usually this) and event data (in this case, a string).
private void OnTransactionApproved(string transactionName)
{
RaiseTransactionApprovedEvent?.Invoke(this, transactionName);
}
Wiring Up and Handling Events:
Subscribe to the event using the += operator and define a method to handle the event when it is raised.
account.RaiseTransactionApprovedEvent += Account_RaiseTransactionApprovedEvent;
private void Account_RaiseTransactionApprovedEvent(object sender, string e)
{
// Code to handle the event
}
Tim explains how to use events to automatically update the browser UI when certain actions occur, such as when a transaction is approved.
Raising Events:
Trigger the custom event after a transaction is approved.
public void AddDeposit(decimal amount, string depositName)
{
// Code to add deposit
RaiseTransactionApprovedEvent?.Invoke(this, depositName);
}
Subscribing to Events in the UI:
Subscribe to the event in the main form to update the transaction list whenever a new transaction is approved.
public MainForm()
{
InitializeComponent();
account.RaiseTransactionApprovedEvent += Account_RaiseTransactionApprovedEvent;
}
private void Account_RaiseTransactionApprovedEvent(object sender, string e)
{
// Update the UI with the new transaction
}
Tim Corey explains the use of the ?.Invoke() syntax when raising events in C#. This modern approach simplifies the code and ensures thread safety.
The question mark (?.) before Invoke is a null-conditional operator. It checks if the event handler is null before invoking it, preventing potential exceptions.
Traditional Approach:
Previously, developers used multiple lines of code to check if the event handler was null and then invoke it.
if (RaiseTransactionApprovedEvent != null)
{
RaiseTransactionApprovedEvent(this, depositName);
}
Modern Approach with ?.Invoke():
The null-conditional operator streamlines this process, performing the null check and invoking the event in one line.
RaiseTransactionApprovedEvent?.Invoke(this, depositName);RaiseTransactionApprovedEvent?.Invoke(Me, depositName)If RaiseTransactionApprovedEvent is null, the invocation does not proceed, effectively avoiding any exceptions.
Benefits:
Tim explains how to listen to custom events in a Windows Forms application and update the UI accordingly.
Subscribing to Events:
Subscribe to the custom event using the += operator.
customer.CheckingAccount.TransactionApprovedEvent += CheckingAccount_TransactionApprovedEvent;AddHandler customer.CheckingAccount.TransactionApprovedEvent, AddressOf CheckingAccount_TransactionApprovedEventEvent Handler Method:
Define a method to handle the event. This method updates the UI based on the event data.
private void CheckingAccount_TransactionApprovedEvent(object sender, string e)
{
// Update the UI with the new transaction
checkingTransactionsDataSource.DataSource = null;
checkingTransactionsDataSource.DataSource = customer.CheckingAccount.Transactions;
checkingBalanceLabel.Text = customer.CheckingAccount.Balance.ToString("C2");
}
Tim demonstrates the entire process of creating, raising, and handling custom events in action.
Triggering the Event:
Raise the event after a transaction is approved or when the balance changes.
private void OnTransactionApproved(string transactionName)
{
RaiseTransactionApprovedEvent?.Invoke(this, transactionName);
}
Handling Multiple Events:
Ensure the application listens to multiple events, such as transactions and balance changes, to keep the UI updated in real-time.
public MainForm()
{
InitializeComponent();
customer.CheckingAccount.TransactionApprovedEvent += CheckingAccount_TransactionApprovedEvent;
customer.SavingsAccount.TransactionApprovedEvent += SavingsAccount_TransactionApprovedEvent;
}
private void SavingsAccount_TransactionApprovedEvent(object sender, string e)
{
// Update the UI with the new savings transaction
savingsTransactionsDataSource.DataSource = null;
savingsTransactionsDataSource.DataSource = customer.SavingsAccount.Transactions;
savingsBalanceLabel.Text = customer.SavingsAccount.Balance.ToString("C2");
}
Running the Application:
Tim shows how to debug events and inspect the information sent with them.
Setting a Breakpoint:
Set a breakpoint in the event handler method to inspect the event data.
private void CheckingAccount_TransactionApprovedEvent(object sender, string e)
{
// Breakpoint here
}
Debugging:
Tim Corey expands on the demo by creating another custom event to handle overdraft situations. This event will be triggered when an overdraft occurs and will notify the user of the overdraft amount.
Tim starts by defining a new event in the Account class for overdraft scenarios:
Event Declaration:
Define the event using the event keyword with a decimal type to pass the overdraft amount.
public event EventHandler<decimal> OverdraftEvent;Public Event OverdraftEvent As EventHandler(Of Decimal)Triggering the Event:
The event is triggered in the part of the code where an overdraft successfully occurs.
if (overdraftSuccessful)
{
OverdraftEvent?.Invoke(this, overdraftAmount);
}
Wiring Up the Event:
Subscribe to the overdraft event in the Dashboard form.
customer.CheckingAccount.OverdraftEvent += CheckingAccount_OverdraftEvent;AddHandler customer.CheckingAccount.OverdraftEvent, AddressOf CheckingAccount_OverdraftEventHandling the Event:
Define an event handler to display a message when an overdraft occurs.
private void CheckingAccount_OverdraftEvent(object sender, decimal e)
{
errorMessage.Text = $"You had an overdraft protection transfer of {e:C2}";
errorMessage.Visible = true;
}
Triggering and Displaying the Event:
When an overdraft occurs, the event triggers and updates the UI to notify the user.
You had an overdraft protection transfer of $20.44
Tim explains how to listen to the same event in multiple languages and forms, demonstrating the versatility of events.
Adding a Label to Another Form:
Add a label to a secondary form to display overdraft messages.
<asp:Label ID="errorMessage" runat="server" Visible="false" />
Subscribing to the Event:
Subscribe to the overdraft event in the secondary form.
customer.CheckingAccount.OverdraftEvent += SecondaryForm_OverdraftEvent;AddHandler customer.CheckingAccount.OverdraftEvent, AddressOf SecondaryForm_OverdraftEventHandling the Event:
Define an event handler to display the overdraft message on the secondary form.
private void SecondaryForm_OverdraftEvent(object sender, decimal e)
{
errorMessage.Visible = true;
}
Simultaneous Event Handling:
Tim demonstrates that the event can be handled in multiple forms simultaneously, ensuring that all relevant parts of the application respond to the event appropriately.
Both the main form and the secondary form display the overdraft message when the event is triggered.
Tim highlights the importance of cleaning up event listeners to prevent memory leaks and ensure proper application performance.
Unsubscribing from Events:
It is crucial to unsubscribe from events before destroying a class instance or closing a form.
customer.CheckingAccount.OverdraftEvent -= CheckingAccount_OverdraftEvent;RemoveHandler customer.CheckingAccount.OverdraftEvent, AddressOf CheckingAccount_OverdraftEventWhy It's Important:
Using Named Methods:
Avoid using anonymous functions for event handlers, as it makes it difficult to unsubscribe from the events.
// Good practice: using named methods for event handlers' Good practice: using named methods for event handlersTim Corey explains the best practices for passing data through events, particularly the benefits of using a class rather than simple data types like string or decimal.
Tim starts by discussing why using simple data types for events is less common and why it's typically better to pass a class:
Flexibility and Scalability:
EventArgs Inheritance:
Tim demonstrates how to create a custom EventArgs class for the overdraft event.
Define the Class:
Create a new class that inherits from EventArgs and includes properties for the data you want to pass through the event.
public class OverdraftEventArgs : EventArgs
{
public decimal AmountOverdrafted { get; private set; }
public string MoreInfo { get; private set; }
public OverdraftEventArgs(decimal amountOverdrafted, string moreInfo)
{
AmountOverdrafted = amountOverdrafted;
MoreInfo = moreInfo;
}
}
Using the Class in the Event:
Update the event declaration to use the custom EventArgs class.
public event EventHandler<OverdraftEventArgs> OverdraftEvent;Public Event OverdraftEvent As EventHandler(Of OverdraftEventArgs)Triggering the Event:
Pass an instance of the custom EventArgs class when invoking the event.
OverdraftEvent?.Invoke(this, new OverdraftEventArgs(amountNeeded, "Additional info"));OverdraftEvent?.Invoke(Me, New OverdraftEventArgs(amountNeeded, "Additional info"))Tim emphasizes the importance of using read-only properties in the EventArgs class to prevent accidental modification of event data.
Avoiding Modifications:
Example of the Problem:
Tim demonstrates how modifying event data in one event handler can affect other handlers if properties are not read-only.
private void CheckingAccount_OverdraftEvent(object sender, OverdraftEventArgs e)
{
e.AmountOverdrafted = 1000; // This modification affects all handlers
}
Solution:
Use private setters and pass data through the constructor to make properties read-only.
public decimal AmountOverdrafted { get; private set; }
public string MoreInfo { get; private set; }
Tim Corey highlights an important exception to the rule of using private setters for event data properties. This exception is when you need to allow event listeners to modify the event data, such as in cases where a transaction might be canceled based on certain conditions.
Tim provides an example where an event handler might need to cancel a transaction. This is achieved by adding a CancelTransaction property to the custom EventArgs class.
Defining the Property:
Add a public property with both a getter and setter to the EventArgs class.
public class OverdraftEventArgs : EventArgs
{
public decimal AmountOverdrafted { get; private set; }
public string MoreInfo { get; private set; }
public bool CancelTransaction { get; set; } = false;
public OverdraftEventArgs(decimal amountOverdrafted, string moreInfo)
{
AmountOverdrafted = amountOverdrafted;
MoreInfo = moreInfo;
}
}
Setting the Property in the Event Handler:
In the dashboard, the event handler can set this property to cancel the transaction.
private void CheckingAccount_OverdraftEvent(object sender, OverdraftEventArgs e)
{
if (denyOverdraft.Checked)
{
e.CancelTransaction = true;
}
errorMessage.Text = $"You had an overdraft protection transfer of {e.AmountOverdrafted:C2}";
errorMessage.Visible = true;
}
Checking the Property in the Source Method:
In the method that triggers the event, check if the CancelTransaction property is set to true before proceeding.
if (args.CancelTransaction)
{
return false; // Transaction is canceled
}
Tim further refines the application to make it more interactive and user-friendly.
Adding a Checkbox for Overdraft Control:
Add a checkbox to the form that allows the user to enable or disable overdraft protection.
private void InitializeComponent()
{
this.denyOverdraft = new System.Windows.Forms.CheckBox();
// Initialize other controls
this.denyOverdraft.Text = "Stop Overdrafts";
this.denyOverdraft.CheckedChanged += new System.EventHandler(this.denyOverdraft_CheckedChanged);
}
Handling the Checkbox State:
In the event handler for the checkbox, update the logic to consider the checkbox state when deciding to cancel a transaction.
private void denyOverdraft_CheckedChanged(object sender, EventArgs e)
{
if (denyOverdraft.Checked)
{
// Logic to stop overdraft transactions
}
}
Updating the Event Handler:
Ensure the event handler respects the checkbox state to allow or deny overdrafts.
private void CheckingAccount_OverdraftEvent(object sender, OverdraftEventArgs e)
{
if (denyOverdraft.Checked)
{
e.CancelTransaction = true;
}
errorMessage.Text = $"You had an overdraft protection transfer of {e.AmountOverdrafted:C2}";
errorMessage.Visible = true;
}
Tim Corey wraps up the tutorial by summarizing key points and best practices for working with events in C#.
Remove Event Listeners:
Always remove event listeners before destroying objects to prevent memory leaks.
Use the -= operator to unsubscribe from events.
customer.CheckingAccount.OverdraftEvent -= CheckingAccount_OverdraftEvent;RemoveHandler customer.CheckingAccount.OverdraftEvent, AddressOf CheckingAccount_OverdraftEventUse EventArgs Inheritance:
Private Setters for Read-Only Properties:
Event Handler Syntax:
EventHandler<T> delegate to define events, providing a clear and consistent pattern for passing event data.Null-Conditional Operator:
Tim Corey's comprehensive tutorial on C# events provides valuable insights and practical examples for creating, handling, and managing events effectively. By following these best practices, developers can create more interactive and responsive applications.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy