Curiosity is
bliss
Julien Couvreur's programming blog and
more

« C#
Delegates strike back   |   Main   |   Archive list   |
ASP.NET:
Automatic field persistence »

April 29, 2003

C# events vs. delegates

We have looked at delegates and their implementation in two previous
articles. But if you searched some more information about delegates on the web,
you surely noticed they are almost always associated with the "event"
construct.
Online event tutorials make it look like events are something
pretty different from regular delegates instance, although related. Events are
explained as if they were a special type or construct, but we'll see they really
are a modifier on the delegate type, that adds some restrictions that the
compiler enforces and also adds two accessors (similar to the get and
set for properties).

A first look at event vs. regular delegate
After getting to
understand delegates pretty thoroughly, I remembered that C# also has a
construct for events. I definitely seemed related with delegates, but it wasn't
clear the difference.
Events looked like a field holding a combination of
delegates, which is just what a multicast delegate is. Also they offer the same
combination syntax as delegates, via the + and - operators.
In
the following sample program (which has no useful functionality what-so-ever) we
see that msgNotifier (with event construct) and msgNotifier2
(plain delegate) appear to behave exactly the same way for all intents and
purposes.

using System;

namespace EventAndDelegate
{
delegate void MsgHandler(string
s);

class Class1
{
public static event
MsgHandler msgNotifier;
public static MsgHandler
msgNotifier2;
[STAThread]
static void
Main(string[] args)
{
Class1.msgNotifier
+= new MsgHandler(PipeNull);
Class1.msgNotifier2 += new
MsgHandler(PipeNull);

Class1.msgNotifier("test");

Class1.msgNotifier2("test2");
}

static
void PipeNull(string s)
{

return;
}
}
}

Looking at the IL code for the Main method in this code, I noticed
that both delegates msgNotifier and msgNotifier2 are again used
exactly the same way.

.method private hidebysig static void Main(string[] args) cil
managed
{
.entrypoint
.custom instance void
[mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
//
Code size 95 (0x5f)
.maxstack 4
IL_0000: ldsfld class
EventAndDelegate.MsgHandler  EventAndDelegate.Class1::msgNotifier

IL_0005: ldnull
IL_0006: ldftn void
EventAndDelegate.Class1::PipeNull(string)
IL_000c: newobj instance
void EventAndDelegate.MsgHandler::.ctor(object,

native int)
IL_0011: call class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Combine(class
[mscorlib]System.Delegate,
class
[mscorlib]System.Delegate)
IL_0016: castclass
EventAndDelegate.MsgHandler
IL_001b: stsfld class
EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier

IL_0020: ldsfld class EventAndDelegate.MsgHandler
EventAndDelegate.Class1::msgNotifier2
IL_0025: ldnull

IL_0026: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_002c:
newobj instance void
EventAndDelegate.MsgHandler::.ctor(object,
native
int)
IL_0031: call class [mscorlib]System.Delegate
[mscorlib]System.Delegate::Combine(class
[mscorlib]System.Delegate,
class
[mscorlib]System.Delegate)
IL_0036: castclass
EventAndDelegate.MsgHandler
IL_003b: stsfld class
EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2

IL_0040: ldsfld class EventAndDelegate.MsgHandler
EventAndDelegate.Class1::msgNotifier
IL_0045: ldstr "test"

IL_004a: callvirt instance void
EventAndDelegate.MsgHandler::Invoke(string)
IL_004f: ldsfld class
EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2

IL_0054: ldstr "test2"
IL_0059: callvirt instance void
EventAndDelegate.MsgHandler::Invoke(string)
IL_005e: ret
} // end
of method Class1::Main

Looking at the C#
keywords list on MSDN. It turns out that event is only a modifier.
The question is what modification does it bring?

The added value of event
Events and
interfaces
First, an event can be included in an interface declaration,
whereas a field cannot. I believe this is the most important behavior change
introduced by the event modifier. For example:

interface ITest
{
event MsgHandler msgNotifier;
// compiles
MsgHandler msgNotifier2; // error CS0525:
Interfaces cannot contain fields
}

class TestClass :
ITest
{
public event MsgHandler msgNotifier; // When you
implement the interface, you need to implement the event too

static void Main(string[] args) {}
}

Event invocation
Then, invoking an event can only be done from
within the class that declared the event, whereas a delegate field can be
invoked by whoever has access to it. For example:

using System;

namespace EventAndDelegate
{
delegate void MsgHandler(string
s);

class Class1
{
public static event
MsgHandler msgNotifier;
public static MsgHandler
msgNotifier2;

static void Main(string[] args)

{
new Class2().test();
}

}

class Class2
{
public void
test()
{
Class1.msgNotifier("test");
// error CS0070:
The event 'EventAndDelegate.Class1.msgNotifier' can only appear on the left hand
side of += or -= (except when used from within the type
'EventAndDelegate.Class1')

Class1.msgNotifier2("test2"); // compiles fine

}
}
}

This restriction on invocations is quite strong. Even derived classes from
the class declaring the event aren't allowed to fire the event. A way to deal
with this is to have a protected virtual method to trigger the event.

Event accessors
Also, events are somewhat like properties, but
instead of having a pair of get and set methods, they have an
add and remove method. You are allowed to override these, as shown
in examples 2 and 3 on this C#
event modifier reference on MSDN. Although I don't see how example 2
is useful, you could imagine that you could have a custom add to send
some notification or write a log entry, for example, when a listener is added to
your event.
The add and remove accessors need to be customized
together, otherwise you get error CS0065
('Event.TestClass.msgNotifier' : event property must have both add and remove
accessors).
Looking at the IL for a previous example, where the event
accessors weren't customized, I noticed compiler generated methods
(add_msgNotifier and remove_msgNotifier) for the
msgNotifier event. But they weren't used, and whenever the event was
accessed the same IL code was duplicated (inlined).
But when you customize
these accessors and look at the IL again, you'll notice that the generated
accessors are now used when you access the event. For example, this code :

using System;

namespace Event
{
public delegate void MsgHandler(string
msg);

interface ITest
{
event MsgHandler
msgNotifier; // compiles
MsgHandler msgNotifier2; //
error CS0525:
Interfaces cannot contain fields
}

class
TestClass : ITest
{
public event MsgHandler
msgNotifier
{
add

{

Console.WriteLine("hello");
msgNotifier += value;

}

}

static void Main(string[]
args)
{
new TestClass().msgNotifier += new
MsgHandler(TestDel);
}
static void
TestDel(string x)
{
}

}
}

brings the following IL for the Main method:

{
.entrypoint
// Code size 23
(0x17)
.maxstack 4
IL_0000: newobj instance void
Event.TestClass::.ctor()
IL_0005: ldnull
IL_0006: ldftn void
Event.TestClass::TestDel(string)
IL_000c: newobj instance void
Event.MsgHandler::.ctor(object,
native
int)
IL_0011: call instance void
Event.TestClass::add_msgNotifier(class Event.MsgHandler)
IL_0016:
ret
} // end of method TestClass::Main

Event signature
Finally, even though C# allows it, the .NET
framework adds a restriction on the signature of delegates that can be used as
events. The signature should be foo(object source, EventArgs e), where
source is the object that fired the event and e contains any
additional information about the event.

Conclusion
We have seen that the event keyword is a
modifier for a delegate declaration that allows it to be included in an
interface, constraints it invocation from within the class that declares it,
provides it with a pair of customizable accessors (add and remove)
and forces the signature of the delegate (when used within the .NET
framework).

Links
Events
Tutorial on MSDN.

Event
keyword reference on MSDN.

Update:
One question that was left open and that was brought up by
some readers was the rationale behind the restriction on event invocation:
"Invoking an event can only be done from within the class that declared the
event". I am still trying to get a definitive answer via some internal
discussion lists, but here is the best idea that I got so far.
I think it is
because of a syntaxic problem. When you put an access specifier ("private",
"public", ...) on an event it controls who can register or listen to that event.

The question is how would you specify the access control for the invocation
of that event. You can't use the same specifiers because it would be
confusing.
The solution is to have the event invocation be completely
restricted and allow the coder to write a custom invocation method on which he
can easily control the access, which is the way it is now.

An alternate solution might have been to use some kind of attribute on the
event [EventAccess(PublicInvocation)] or [EventAccess(ProtectedInvocation)]. But
that seems uglier because it requires reflection to control the access at
runtime.

Update:
Race condition in common event firing
pattern:
As any other object, an event object needs to be treated with
care in multi-threaded scenarios.

JayBaz
and EricGu
point out a frequent race condition mistake with event firing:

if (Click != null)
Click(arg1,
arg2);

Note that all the MSDN samples I have seen use the dangerous
pattern.

Posted by Julien. Permalink

Comments

Great material. Exactly what I needed to understand. The Visual Studio
documentation didn't make this clear at all.

Notes:
- Your use of the phrase "delegate declaration" is ambiguous: do
you mean the declaration of a delegate *type* [e.g.: public delegate
MyDelegate(int i)] or the declaration of a delegate *object* [e.g.: MyDelegate
a] ?

- I would like to know the rationale behind why the event's invocation is
restricted to the declaring class
Posted by: JulioB at October 2, 2003 10:57 PM

Glad this was useful to you.

You're right, from the ECMA C# language specification, "delegate declaration"
is the term for the declaration of the delegate type (using the "delegate"
keyword). Whereas "event" is used in the context of declaring a member variable,
not declaring a type.

I don't know why events can only be fired by class that declare the event. In
most cases you end up adding a method (usually with the On* prefix) to get
around this restriction. I'll try to find more about the reason for this
design.
Let me know if you find that information before I do.
Posted by: Dumky at October 3,
2003 11:30 PM

Thanks for the article - it was very useful.

An example of the relevance of the add/remove accessors on events is when you
use them on remoted objects. (eg. Client connects to remoted object on a server
and subscribes to state change events). If a remoted object exposes an event,
but no delegates have been registered to the event, then there is no need to
source the information needed to raise the event (or raise the event for that
matter). There may be a high cost in starting/running the process that sources
the event information. Adding event accessors allows you to hook the
introduction/removal of the delegate.

This is also true of non-remoted objects.
Posted
by: tingeyp at October 15, 2003 03:26
AM

Useful article, much appreciated!

I have a quick question about derived classes raising parent class events.
What is the rationale for not allowing a subclass to raise a parent class's
events. I can obviously understand classes outside the hierarchy not being able
to, but subclasses? It just isn't clear.
Posted by:
skarab at November 10, 2003 01:29 PM

Ignore my last comment. I didn't read others' comments before posting. Shame
on me!
Posted by: skarab at November 10, 2003 01:46 PM

No problem. I just updated the post with the rationale that makes the most
sense so far.
I'm also trying to get an explanation from somebody on the CLR
or C# team.
Posted by: Dumky at November 10, 2003
07:54 PM

Great Efforts..I really appriciate the examples and explanation.
Posted by: Rafi at
February 15, 2004 08:20 PM

Excellent article. Thanks much.
Posted by: Rocky at March 25, 2004 12:31 PM

Nice article. I did not find this information anywhere else.
Thanks..
Posted by: Rajeev at May 1, 2004 03:53 PM

A cleaner solution now at http://blogs.msdn.com/jaybaz_ms/archive/2004/06/17/158636.aspx

Posted by: jaybaz
[MS] at June 17, 2004 02:28 PM

very good and drilled down.
Posted by: Ali Asghar Ahmed at July 4, 2004
01:12 AM

Thanks a lot!

I never was exactly sure what "event" did, even after reading all about it in
"Inside C#". The only purpose I thought it served was to let the VS.NET IDE list
it as an available "Event" for the class! (Which is also an important
side-effect.)

Your explanation was well appreciated
Posted by:
Robert at August 19, 2004 03:06 PM

I have a basic question and i am new to C#.
Is there anyway, i can
shadow/hide a Parent Controls event.

I am trying to do this

public new event EventHandler SelectedIndexChanged (object sender,
SelectedIndexChangedEventArgs e);

Is this a right approach?
I like to hide the regular event and write my
own ? Thanks
Posted by: Bob at September 2, 2004 01:00 PM

Hi,

Great post, The restriction on the events that "Invoking an event can only be
done from within the class that declared the event" is not easily understood,
may it is to place more emphasis on the class which defines the event, for it to
decide and fire the event.

I have given a example stating this difference between events and delegates
at: http://narasimhagm.blogspot.com/2004/11/c-events-and-delegates.html

Regards
Narasimha G. M.
Posted by: Narasimha G. M. at January 1, 2005
01:46 AM

This is a great article . Thanks
Posted by: Vijaya at January 6, 2005 10:08
PM

Great Article. Exactly what i needed.
Posted by:
Ganesh at January 7, 2005 01:13
AM

Great Work - Found it very Useful.
Posted by: Gautham Chuliyill at January 17,
2005 11:58 PM

Thanks a lot.
You helped me a lot.
Posted by:
Fernando Miranda at January 19, 2005
10:52 AM

Above, you mention a race condition

if (Click != null)
Click(arg1, arg2);

This is interesting. You never had this problem in Java. You could follow the
example here....http://java.sun.com/j2se/1.5.0/docs/api/index.html

and did not need to do any synchronization. Being able to do stuff without
synchronization sometimes helps performance problems that were caused by
contention. I guess I could always fall back to the way Java does listeners in
C#.
Posted by: Dean Hiller at March 31, 2005 11:33
AM

Dean, the problem is that events in C# are not simply listeners, they are
listener collections.
What if the collection is modified as it gets iterated
over for sending the events?

In Java you would still have a problem if somebody (ie another thread)
removed the listener object between the null check and the actual call.
Posted by: Julien
Couvreur at March 31, 2005 11:37 AM

Very useful - thanks much.

Posted by: netshade at April 21, 2005 11:23 AM

What a post. Beauty !
But I am somewhat puzzled by you remark on the
restriction of the delegate's signature you can apply the events keyword upon.
Testing it I did not find any problems in using different signatures. And when
it comes to COM events, where you really need the event keyword, the
System.EventHandler 's signature does not make any sense.
What am I missing
?
Posted by: Peter van Ooijen at
August 31, 2005 12:40 PM

Hey Peter,

Thanks.
Regarding the signature restriction on events in .Net, I'm
actually
not sure. I've never seen that restriction in action. It might
only
apply for CLS-Compliant code.
Re-reading the MSDN doc on the topic,
it's still not quite clear:

".NET Framework Guidelines

Although the C# language allows events to use any delegate type, the
.NET
Framework has some stricter guidelines on the delegate types that
should be
used for events. If you intend for your component to be used
with the .NET
Framework, you probably will want to follow these
guidelines.

The .NET Framework guidelines indicate that the delegate type used for
an
event should take two parameters, an "object source" parameter
indicating the
source of the event, and an "e" parameter that
encapsulates any additional
information about the event. The type of
the "e" parameter should derive from
the EventArgs class. For events
that do not use any additional information,
the .NET Framework has
already defined an appropriate delegate type:
EventHandler."
(from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkEventsTutorial.asp)

Let me know if you find any clear explanation.
Posted by: Julien
Couvreur at September 2, 2005 06:07 PM

I read some post comment on events & delegates but Still I don't clear
the concept of events & delegates.Please help me.
Posted by: PRASHANT at September 22, 2005
10:23 PM

Hey Prashant,

You should be able to find a lot of info online. Events are just some
restrictions on top of delegates, so you should focus on understanding delegates
first.
Delegates are essentially method pointers. Very useful for callback
scenarios, ie. I call a component asynchronously passing in a callback method,
the call returns and, later, the callback gets called back.
It's also used a
lot in winforms code, for notifications: this button was clicked, this window
was closed,...
Posted by: Julien Couvreur at September 23, 2005 07:31
PM

Hey,
thanks a lot for this article. It was of a great help for
me.

Posted by: Brijesh Choubey at September 29, 2005
03:06 AM

I was searching for exactly this.
One more difference wrt VisualStudio
(although doesnt have anything to do with language but makes difference while
desiging applications),
When you mark something "event", the designer
displays it in event-property window.
Posted by: Sandeep at October 27, 2005 05:37
PM

Okay, so this is quite a few months later, but:

if you use a delegate directly, an outside object can invoke it when really,
you just want the owning object to do the invoking. With the event keyword
modifier, you can no longer call invoke on it.

Hope that helps anyone who stumbles upon this.
Posted by: Mr. Raybell at
March 10, 2006 04:28 PM

Does the code you suggest (well, actually, other people suggest) really fix
the race condition problem?

It seems to me that the race condition has not been fixed by this adjustment.
Aren’t you just ensuring you "win the race" by copying out the event to
temporary variable? You are only guaranteeing that collection you have is not
empty when you invoke it, but you have not guaranteed that the subscriber is
still valid. Let me give you an example.

Suppose I create a new dialog box and in that dialog box I subscribe to your
event. In my dialog box, I choose to subscribe to your event. The user clicks OK
on my dialog box, and your thread, at that moment, decides to fire. It copies
the event list, and checks to see if it is null. Now my thread continues to run.
In my main form, I destroy the dialog box, removing my hander from the real
event (not the copy), and get ready to call gc.collect(), but just before I do,
your thread keeps running. My object is “gone” enough to not work properly, but
not “gone” enough to be recognized as non-existent by the gc. It has destroyed
all of its internal variables, and will crash if called because the code will
throw exceptions due to the variables being no longer initialized. I am not
expecting a call any more. But your thread is just now invoking the event with
its saved copy.

What happens? Does the event just not get invoked for some reason? I can’t
see why that would be… If it does get invoked, bad stuff for sure will
happen.

Unless there is some behind-the-scene reason why the handler will not
actually be called -- how can it know this if it doesn’t know the object has
been gc’ed? – bad stuff will happen. All the copy has done is make the race
condition more complicated, more difficult to catch, it hasn’t eliminated
it.

It seems the only way to eliminate the problem is either 1) to put some kind
of lock on the event variable and get that lock before triggering the event (the
event handler can still remove itself, just ignore the lock. If you’ve already
been activated, there’s no worry that you’ll mess stuff up, assuming the code
behind events is properly written…) or 2) to never let more than one thread deal
with any given event/handler group.

I suspect the authors intentionally assumed you would not attempt to use
events with multiple threads in this way.

I’m not certain I’m right, but this certainly bothers me!

Posted by: Chiem at April 11, 2006 10:59
AM

Perfect! I've been reading up on delegates and events and couldn't figure out
why we needed events at all. This answered my question exactly!

Thanks!
Posted by: ian at July 24, 2006 03:41 PM

It really helped me in finding some differences.
Posted by: Arif
Sarwar at August 7, 2006 12:55 PM

Trackbacks

More details on events vs. delegates
Excerpt:

Weblog: ISerializable
Tracked: March 29, 2004 10:58
AM

The
dark side of C# Delegates
Excerpt: The dark side of C#
Delegates
Weblog: dudu
Tracked: May 23, 2004 04:00 AM

re: Events versus Delegates
Excerpt:
Weblog:
Dan Vallejo's WebLog
Tracked: May 23, 2004 08:53 AM

C#
Events vs. Delegates, Race Conditions and testing for
Null...
Excerpt:
Weblog: Eladio
Tracked: July
27, 2005 06:20 AM

C# Events
vs. Delegates, Race Conditions and testing for Null...
Excerpt:

Weblog: Eladio
Tracked: July 30, 2005 12:56 PM

The C# event keyword is an access modifier for delegate
members
Excerpt: Recently I had trouble getting COM events to work
in a COM automation server written in C#. A visitor's...
Weblog:
Peter's Gekko
Tracked: September 1, 2005 08:54 AM

C# Events vs. Delegates, Race Conditions and testing for
Null...
Excerpt: Here is an interesting article on the differences
between Events and Delegates...  The author gets...
Weblog:
Eladio Martin
Tracked: October 18, 2005 05:53 PM

Post a comment

Name:

Email Address:

URL:

Your email address won't be published on the
site if you also input a URL.
Note: your comment may not appear in your
browser immediately, even if it is successfully posted, because the pages of
this blog use HTTP caching.

Remember personal info?
YesNo
Comments:

Recent Entries

All entries
(217)

Web API
authentication for mashups
How to expose click
fraud?
FlashXMLHttpRequest:
cross-domain requests
Highway traffic map for
cellphones
XmlHttpRequest Debugging
for IE
Javascript
Shell bookmarklet for IE
LiveClipboard: Wiring web
and desktop apps together
Computational
Camera
Cross-domain AJAX using
Flash
Two amazing
demos: Mooncheese and Spore
"MSDN - Fix titles"
Greasemonkey script
"Add Torrent to ABC"
Greasemonkey script
Comment Tracking with
coComment
"Take
It With You" Feed Reader
Lock bumping (lockpicking
technique)
"Take
It With You" Wiki
XmlHttpRequest Debugging
(Firefox 1.5 update)
Greasemonkey Hacks is
out
XmlHttpRequest Debugging
(update)
ArsTechnica - Single Page
View