Target of this Article
This article is aimed at beginners to events. More advanced users may find some of the topics interesting if they've not investigated them before, or haven't used them for a while. It is a complete rewrite of my original article, which left a lot to be desired in the way of examples and explanation.
This article is NOT an in depth study of the underlying principles and processes used in event/delegate handling. There are many other great articles and blog entries out there that cover that stuff. In fact, the inspiration for this article came from my early experiences in attempting to use and understand events, but becoming totally confused by articles of that type. Once you've finished reading this article and have grasped the concepts I explore, do go ahead and read those other articles. They will then be much clearer!
Prerequisites
You should have some C# coding experience, know how methods and properties are constructed, parameters are passed and values are returned and a knowledge of access modifiers as these will not be explained here.
What Is An Event?
An event is... well - an event! Something, somewhere has happened. Maybe a button has been clicked, a value has changed, or is about to change and we have the opportunity of cancelling it. Whenever something happens to an object, the likelihood is that there is an event that can be used. We can easily use events in our own classes to broadcast to the world when something happens. An important point is that we don't know if anybody is interested in our event(s), there could be 0 to ∞ interested parties, and we know nothing about them!
Index
Simple Class
So let's get started. For this article, I have used a normal Windows Forms application. Add a class SimpleClass
and add an instance of it to Form1
so you end up with this:
using System;
namespace YourNamespace
{
public class SimpleClass
{
}
}
using System;
using System.Windows.Forms;
namespace YourNamespace
{
public partial class Form1 : Form
{
private SimpleClass simpleClassInstance = new SimpleClass();
public Form1()
{
InitializeComponent();
}
}
}
Simple Event
So how do we create an event? Easy! From 2.0 all we have to do is add this to our SimpleClass
(1.1 users will need to do it 'the old fashioned way' - see Simple Event With Delegate):
public event EventHandler SimpleEvent;
This creates the event, but it's never used at the moment which the compiler will warn about if you attempt to build now. The EventHandler
part is the magic that allows us to communicate with many different objects that we actually have no knowledge of. For those interested, hover your mouse over it and you'll see it's actually a delegate. For now, don't worry about what a delegate is, or what it does. I'll go into a little detail when we need to later on.
Now let's create a method that actually raises this event:
protected virtual void OnSimpleEvent(EventArgs e)
{
EventHandler eh = SimpleEvent;
if (eh != null)
eh(this, e);
}
So what's all this other stuff? Let's go one line at a time:
protected virtual void OnSimpleEvent(EventArgs e)
We're simply creating a method called OnSimpleEvent
that takes a parameter of type System.EventArgs
. EventArgs
is the base class that is used in almost all event handling. We'll investigate this more in the next example.
EventHandler eh = SimpleEvent
Our SimpleEvent
that we previously created is already of the delegate type EventHandler
. We're simply creating a copy of this to work with.
if (eh != null)
If no one is listening for (subscribed to) our event, then it will be null
, so we need to check this now to avoid a NullReferenceException
.
eh(this, e)
The EventHandler
(that is the type of our event) needs two parameters: object sender and EventArgs e
. We simply send ourselves (this
) and the EventArgs
we took as a parameter into this method.
We can now raise the event by calling this method, but it's protected. OnEvent
methods should never be called directly from external sources. So let's create another method that will in turn call this method.
public void PerformSimpleEvent()
{
OnSimpleEvent(EventArgs.Empty);
}
Now we have a public
method we can call from our form. Add a Button
to Form1
and in it's Click handler (just double click it in the designer), call the simpleClassInstance
's public
method.
private void btnSimpleEvent_Click(object sender, EventArgs e)
{
simpleClassInstance.PerformSimpleEvent();
}
Now all we need to do is subscribe to the SimpleEvent
. In the Form1
constructor, type simpleClassInstance.SimpleEvent+=
and intellisense will pop up the rest for you. Press TAB twice and it will fill the line and create a method for you too. You should have something like this:
using System;
using System.Windows.Forms;
namespace YourNamespace
{
public partial class Form1 : Form
{
private SimpleClass simpleClassInstance = new SimpleClass();
public Form1()
{
InitializeComponent();
simpleClassInstance.SimpleEvent +=
new EventHandler(simpleClassInstance_SimpleEvent);
}
void simpleClassInstance_SimpleEvent(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void btnSimpleEvent_Click(object sender, EventArgs e)
{
simpleClassInstance.PerformSimpleEvent();
}
}
}
Notice the signature of the method matches that of EventHandler
! Obviously, we don't want to throw an exception when the event is raised. We'll just show a MessageBox
, so replace the 'throw
' line with:
MessageBox.Show("SimpleEvent raised");
Now run the code and click the button.
If that's all clear, then read on. If not, go back and read it through again as you'll need to understand it for the following sections.
Passing Data with Custom EventArgs
Passing data is basically a matter of creating our own class that derives from EventArgs
. Create a new class called SimpleEventArgs
and paste in the following (change the namespace to the one you're using):
using System;
namespace YourNamespace
{
public class SimpleEventArgs : EventArgs
{
private int m_Value;
public SimpleEventArgs(int value)
{
m_Value = value;
}
public int Value
{
get { return m_Value; }
}
}
}
This time our event declaration will look a little different. Add these to our SimpleClass
.
public event EventHandler<SimpleEventArgs> SimpleWithArgsEvent;
private int m_Value = 0;
The EventHandler
delegate we used earlier has a generic counterpart EventArgs<>
that allows us to specify any other class that derives from EventArgs
. We've also added a field to hold an int
value. This time we're going to raise the event when a property value changes:
public int Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
OnSimpleWithArgsEvent(new SimpleEventArgs(m_Value));
}
}
}
And add the OnEvent
method:
protected virtual void OnSimpleWithArgsEvent(SimpleEventArgs e)
{
EventHandler<SimpleEventArgs> eh = SimpleWithArgsEvent;
if (eh != null)
eh(this, e);
}
Notice, all we've done is change the eh
declaration so it's the same as the event's. Add another Button
to Form1
and add a Click handler again. In it, increment the new property we created.
private void btnSimpleWithArgsEvent_Click(object sender, EventArgs e)
{
simpleClassInstance.Value++;
}
Back to the constructor, subscribe to the new event and auto generate the method. This time we're going to display the value in our SimpleEventArgs
in a MessageBox
.
void simpleClassInstance_SimpleWithArgsEvent(object sender, SimpleEventArgs e)
{
MessageBox.Show(String.Format("Value = {0}", e.Value));
}
Run the code and click the button a few times.
Cancelable Events
Cancelable events are much the same as any other with a couple of exceptions. They need to derive from CancelEventHandler
plus use CancelEventArgs
(which is annoyingly in the System.ComponentModel
namespace so you'll need to add that to the usings in both Form1
and SimpleClass
) and we need to check that CancelEventArgs.Cancel
is false
before allowing whatever it is that raises the event to commit. In the following example, it's a property change. To facilitate this, the possible value is saved in a variable, and the corresponding member variable is only changed after e.Cancel
has been checked.
In SimpleClass
:
public event CancelEventHandler SimpleCancelableEvent;
private string m_Text = string.Empty;
private string newText;
public string Text
{
get { return m_Text; }
set
{
if (m_Text != value)
{
newText = value;
OnSimpleCancelableEvent(new CancelEventArgs());
}
}
}
protected virtual void OnSimpleCancelableEvent(CancelEventArgs e)
{
CancelEventHandler eh = SimpleCancelableEvent;
if (eh != null)
{
eh(this, e);
if (!e.Cancel)
m_Text = newText;
}
}
And again, add a button
to Form1
, add the Click handler and in it change the Text
property we created above.
private void btnSimpleCancelableEvent_Click(object sender, EventArgs e)
{
simpleClassInstance.Text = "Hello World!";
}
Subscribe to the new event and auto generate the method, and set e.Cancel
in the new method.
void simpleClassInstance_SimpleCancelableEvent(object sender, CancelEventArgs e)
{
e.Cancel =
!(MessageBox.Show(
"Allow Text to change?",
"Cancelable Event",
MessageBoxButtons.YesNo
, MessageBoxIcon.Question)
== DialogResult.Yes);
}
The SimpleClass.Text
property will only change if e.Cancel
is false
... sometimes! To understand this, we need to look into the behind the scenes stuff in a bit more detail.
Delegates (and Multicast) Oversimplified
So, what is a delegate? Good question. MSDN and many other sites start talking about them being "similar to function pointers in C++ ". Not much help if you've never used C++, but let's try and break that seemingly simple statement down.
A function in C++ is the same as a method in C#.
A pointer literally points to a location in memory.
So, a function pointer basically knows where a particular method is stored so it can invoke it by using that particular part of the RAM. That's what our EventHandler
has been magically doing for us. In fact, it's been even more magical. If there was more than one subscriber to our event, it kept track of each one. It does that using an invocation list - a collection of methods that are to be invoked when the event is raised.
Why do we need to know this? Well, in the last example, all the subscribers will have been called - so which one sets the Cancel? The last one in the list, so if the first or nth one attempted to cancel, it would have been overruled by the very last method invoked.
Event Cancelable From Any Subscriber
The new event and property are much the same as before, but the OnEvent
method is slightly different.
public event CancelEventHandler SimpleCancelableAnyEvent;
private string m_TextAny = string.Empty;
private string newTextAny;
public string TextAny
{
get { return m_TextAny; }
set
{
if (m_TextAny != value)
{
newTextAny = value;
OnSimpleCancelableAnyEvent(new CancelEventArgs());
}
}
}
protected virtual void OnSimpleCancelableAnyEvent(CancelEventArgs e)
{
CancelEventHandler eh = SimpleCancelableAnyEvent;
if (eh != null)
{
bool cancel = false;
foreach (CancelEventHandler subscriber in eh.GetInvocationList())
{
subscriber(this, e);
if (e.Cancel)
{
cancel = true;
break;
}
}
if (!cancel)
m_TextAny = newTextAny;
}
}
We get the invocation list from the event, and call each entry in the list via its individual delegate. If any one of them sets Cancel
to true
, we break out and cancel.
Cancelable Event With Args
Of course, there's nothing to stop us passing our own derivation of CancelEventArgs
, all we need to do is change the various bits of code to EventHandler<SimpleCancelEventArgs>
(assuming that's what we call our custom args). There's an example in the demo if you need it.
Asynchronous Events
This is most often not a problem, but all our code so far is blocked by the invoked method(s). Should a method for any reason (maybe it does a large amount of file copying for example) take a long time before it returns, then our SimpleClass
instance is also blocked until the invoked method returns. This situation can be alleviated by calling each delegate in the invocation list asynchronously. This is done by calling the delegate's BeginInvoke
method. BeginInvoke
does exactly what it says - it begins the invocation of the method only, it doesn't wait around for it to finish but returns immediately. The beauty is, it does it on a separate thread for us, so it doesn't hold up the thread we're using for everything else. This is an example of another new event for our SimpleClass
.
public event EventHandler SimpleAsyncEvent;
public void PerformSimpleAsyncEvent()
{
OnSimpleAsyncEvent(EventArgs.Empty);
}
protected virtual void OnSimpleAsyncEvent(EventArgs e)
{
EventHandler eh = SimpleAsyncEvent;
if (eh != null)
foreach (EventHandler subscriber in eh.GetInvocationList())
subscriber.BeginInvoke(
this, e, new AsyncCallback(
OnAsyncCompleted), subscriber);
}
protected virtual void OnAsyncCompleted(IAsyncResult asyncResult)
{
EventHandler subscriber = (EventHandler)asyncResult.AsyncState;
subscriber.EndInvoke(asyncResult);
}
This is very similar to what we did in the previous example. Instead of calling subscriber(this, e)
we call its BeginInvoke
method which takes the same values plus two additional parameters, AsyncCallback
and object
. AsyncCallback
is a delegate for a method in our class that will be called (called back) when the invoked method finally returns. This additional method must take a parameter of IAsyncResult
to match the AsyncCallback
delegate. We get the originally invoked method and call EndInvoke
on it to release the thread.
The downside of this is: If the method invoked needs to do anything on the main thread (updating the UI for example) then you will get a cross thread operation error. We can get around this in Form1
by invoking a separate method.
private void btnSimpleAsyncEvent_Click(object sender, EventArgs e)
{
simpleClassInstance.PerformSimpleAsyncEvent();
}
void simpleClassInstance_SimpleAsyncEvent(object sender, EventArgs e)
{
BeginInvoke(new MethodInvoker(
delegate() { OnSimpleAsync(sender, e); })
);
}
void OnSimpleAsync(object sender, EventArgs e)
{
MessageBox.Show("SimpelAsynceEvent raised");
}
Note: We called BeginInvoke
here on Form1
which is a control. Control.BeginInvoke
does not require a matching EndInvoke
unlike delegate.BeginInvoke
. There is some debate on this issue so I advise a good web search if you are concerned (have a :beer: handy because you might be there a while!).
Simple Event With Delegate
OK, so back to some more simple stuff! Now we're familiar with the idea of delegate
s, we can construct a new version of our first event without using EventHandler
, but declaring our own delegate
instead.
public delegate void SimpleDelegateAndEventHandler(object sender, EventArgs e);
public event SimpleDelegateAndEventHandler SimpleDelegateAndEvent;
Here we have made the return value of the delegate void
and the parameters are the usual for a basic event signature. That's all there is to it. The rest of the code will be exactly as before, except referring to our delegate
instead, and the new event of course.
Different Event Signatures
There is nothing to say we always have to have the 'sender, e
' signature for a method. That is the standard and I recommend you observe it, but we should examine how we can change it to suit our needs. All we need to do is to use our own delegate
and the signature we desire (or none at all if no data is to be passed).
public delegate void SimpleIntegerEventHandler(int value);
...
eh(value);
When we create our subscriber, the method signature will match ...MethodName(int value){...}
Returning Values
In the same way that we changed the delegate
signature above, we can change the return type to get a return value from the invoked method(s). Again, I would recommend following the standards and using/checking your custom event args, but you can do it this way if you wish.
public delegate bool SimpleReturnValueEventHandler();
...
if (eh != null)
{
bool result = eh();
Many times on the CodeProject forums, people ask about passing values from one form to another. All too often people give solutions, that even though they work, are bad practice. Let's construct a common example, a typical substandard answer, and an easy solution using events.
"I have two forms, Form1
and Form2
. Form1
creates Form2
, which has a TextBox
on it. I need to get the value of that text box into Form1
. How do I do it?"
A typical answer goes something like this...
"Change the constructor of Form2
to take a Form1
parameter, have a public
method/property in Form1
and pass the text to that."
private Form1 m_Form1;
public Form2(Form1 form1)
{
InitializeComponent();
m_Form1 = form1;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
m_Form1.ReceiveText(textBox1.Text);
}
So what's wrong with that - it works doesn't it? Well, yes it does, but now Form2
is coupled to Form1
. If Form2
ever needs to be used without Form1
you're stuck. Form2
is nothing to do with Form1
so it shouldn't know anything about it. What if other objects need to know about the Text
- you can probably see how this way is quickly going to get really messy.
Solution (OOP practices observed):
If the TextBox
's text
changing may be of interest to anyone else, the form should raise an event accordingly.
using System;
using System.Windows.Forms;
namespace YourNamespace
{
public partial class Form2 : Form
{
public event EventHandler<UpdateTextEventArgs> UpdateText;
public Form2()
{
InitializeComponent();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
OnUpdateText(new UpdateTextEventArgs(textBox1.Text));
}
protected virtual void OnUpdateText(UpdateTextEventArgs e)
{
EventHandler<UpdateTextEventArgs> eh = UpdateText;
if (eh != null)
eh(this, e);
}
}
}
using System;
namespace YourNamespace
{
public class UpdateTextEventArgs : EventArgs
{
private string m_Text;
public UpdateTextEventArgs(string text)
{
m_Text = text;
}
public string Text
{
get { return m_Text; }
}
}
}
private void btnFormCommunication_Click(object sender, EventArgs e)
{
Form2 form2 = new Form2();
form2.UpdateText += new EventHandler<UpdateTextEventArgs>(form2_UpdateText);
form2.ShowDialog(this);
form2.UpdateText -= form2_UpdateText;
form2.Dispose();
}
void form2_UpdateText(object sender, UpdateTextEventArgs e)
{
Text = e.Text;
}
Conclusion
I have tried to be as clear as possible and build gradually throughout this article to give a fairly decent introduction to the world of events. I hope you've found it understandable, and will find it a useful reference in future. All the sections have a corresponding working example in the demo code attached.
All comments are welcome, just post below.
History
- 22nd January, 2008: Initial version
- 14th February, 2009: Complete rework with demo and comprehensive examples