Title: Make a class define and raise events in C#
This example uses a BankAccount class to manage a bank account (and to show how to define and raise events). If the program tries to remove more money from the account than its current balance, the BankAccount object raises an Overdrawn event. The event includes two parameters: an object named sender that holds a reference to the BankAccount object that is raising the event, and an OverdrawnArgs object.
The OverdrawnArgs object includes two useful properties. The DebitAmount property indicates the amount that the program is trying to remove from the account. The Allow property is a Boolean that the program can set to true to tell the BankAccount object to process the debit even though that will give the account a negative balance.
A program needs to follow five main steps to define and raise events.
Define the EventArgs Class
The object that the class passes to the event handler should inherit from EventArgs. The following code shows the OverdrawnArgs class used by this example.
// Used to hold information when the account is overdrawn.
class OverdrawnArgs : EventArgs
{
// The amount being subtracted.
public decimal DebitAmount;
// Default is to not allow the account
// to have a negative balance.
public bool Allow = false;
}
This class provides the debit amount that is causing the account to be overdrawn. The Boolean Allow field lets the main program tell the object whether it should allow the debit even though that will give the account a negative balance.
Note that the OverdrawnArgs class doesn't include the account's current balance. The main program can get that from the BankAccount object itself. It could not get the debit amount in that way, so that is included in the OverdrawnArgs class.
(For bonus points you could make DebitAmount be a read-only property. You could place it and the BankAccount in an assembly and use the internal keyword to let the BankAccount class set the value but not let the main program set it. Or you could make the constructor set the value. Usually programmers just leave this kind of property read-write and then ignore any changes made by the program.)
Define the Event
The BankAccount class needs to define the event. The event specifies the delegate type of the event handler. You can define the delegate type explicitly as in the following code.
// Raised if the program tries to debit more
// than the account's balance.
public delegate void OverdrawnEventHandler(
object sender, OverdrawnArgs args);
public event OverdrawnEventHandler Overdrawn;
What this code means is the Overdrawn event should be handled by an event handler that takes two parameters: an object named sender representing the object that raised the event and an OverdrawnArgs object.
Alternatively you can use the predefined EventHandler delegate type as shown in the following code.
public event EventHandler<OverdrawnArgs> Overdrawn;
This statement is roughly equivalent to the previous two statements that first defined a separate delegate type.
This version is easier (and was the whole point behind Microsoft inventing the predefined EventHandler type), so that's the way this example does it.
Raise the Event
Having defined the event, the class should raise it whenever appropriate. The following code shows how the BankAccount class raises the event.
// The account's current balance.
public decimal Balance { get; set; }
// Subtract from the account.
public void Debit(decimal amount)
{
if (amount < 0) throw new
ArgumentOutOfRangeException(
"Debit amount must be positive.");
// See if the account holds this much money.
if (Balance >= amount)
{
// There is enough money. Subtract the amount.
Balance -= amount;
}
else
{
// There isn't enough money.
// Raise the Overdrawn event.
if (Overdrawn != null)
{
// Make the OverdrawnArgs object.
OverdrawnArgs args = new OverdrawnArgs();
args.DebitAmount = amount;
// Raise the event.
Overdrawn(this, args);
// If the program wants to allow the account
// to have a negative balance, remove the money.
if (args.Allow) Balance -= amount;
}
}
}
The Balance field holds the account's current balance.
The Debit method is where the class raises the event. First, the method checks whether the amount being debited is less than zero and throws an exception if it is.
Next, the method checks the account's balance. If the balance is big enough to cover the debit, the method subtracts it from the balance.
If the balance is insufficient, the method raises the Overdrawn event. The statement if (Overdrawn != null) checks whether any code has subscribed to the event. It's a strange syntax, but if there are no event handlers registered for the event, then Overdrawn is null so the method doesn't need to raise the event. In fact, if it tries to raise the event, it receives an "Object reference not set to an instance of an object" exception.
If an event handler is subscribed to the event, the method creates an OverdrawnArgs object and sets its DebitAmount. It then uses the syntax Overdrawn(this, args) to raise the event, passing the current object and the OverdrawnArgs object to the subscribed event handlers.
When the event handlers return, the method checks the OverdrawnArgs object's Allow value to see if it should debit the account anyway. If Allow is true, the method subtracts the debit amount from the current balance.
Subscribe to the Event
If the class you are making is a component or control, you can use the Properties window's event display to create event handlers for it. Otherwise you need to subscribe to event handlers in the code.
The following code shows how the main program initializes its BankAccount object and subscribes to that object's Overdrawn event.
// The BankAccount object this program manages.
private BankAccount TheBankAccount;
// Initialize the BankAccount object.
private void Form1_Load(object sender, EventArgs e)
{
TheBankAccount = new BankAccount();
TheBankAccount.Balance = 100m;
txtBalance.Text = TheBankAccount.Balance.ToString("C");
// Subscribe to the Overdrawn event.
TheBankAccount.Overdrawn += Account_Overdrawn;
}
The form's Load event handler creates the BankAccount object, gives it a $100.00 balance, and displays the current balance.
It then registers the Account_Overdrawn method described next to handle the BankAccount object's Overdrawn event.
Handle the Event
When the BankAccount object raises its Overdrawn event, the following Account_Overdrawn event handler executes.
// Handle the account's Overdrawn event.
private void Account_Overdrawn(object sender, OverdrawnArgs e)
{
// Get the account.
BankAccount account = sender as BankAccount;
// Ask the user whether to allow this.
if (MessageBox.Show("Insufficient funds.\n\n Current balance: " +
account.Balance.ToString("C") + "\n Debit amount: " +
e.DebitAmount.ToString("C") + "\n\n" +
"Do you want to allow this transaction anyway?",
"Allow?", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
== DialogResult.Yes)
{
// Allow the transaction anyway.
e.Allow = true;
}
}
This method converts the sender parameter into a BankAccount object. (This is the first step in many event handlers. It's too bad the predefined EventHandler delegate type doesn't take two type parameters so you could make the sender parameter have type BankAccount instead of object. Then you could skip this step. Sadly that's not the way EventHandler is defined.)
Next, the event handler displays a message box giving information about the transaction and asking the user if it should allow the transaction. If the user clicks Yes, the code sets the OverdrawnArgs object's Allow field to true so the BankAccount object that raised the event knows that it should allow the transaction.
This may seem like a lot of steps, but it's not too bad once you get used to it.
Download the example to experiment with it and to see additional details.
|