Events with accessors

H

Harlan Messinger

I've found information on an explicit way of declaring events, similar
to the way a property is declared:

private EventHandler myEvent;
public event EventHandler MyEvent
{
add
{
lock (this)
{
myEvent += value;
}
}
remove
{
lock (this)
{
myEvent -= value;
}
}
}

Here it is explicitly stated that when handlers are added to the event,
they should be added to the delegate _myEvent, and likewise for removal.
When the event is *raised*, however, I don't understand where this tells
the application to look at _myEvent for the handler(s) to be called.

The add and remove accessors both correspond to the set accessor in a
property declaration, but there isn't anything that corresponds to the
get accessor. The code above seems similar to

private int number;
public int Number
{
set
{
number = value;
}
}

and then expecting that when the statement

int x = Number;

is executed, the application will know to get the required value from
the private field number, when I haven't included a get accessor to make
it so.

What am I missing?
 
J

Jon Skeet [C# MVP]

Harlan Messinger said:
I've found information on an explicit way of declaring events, similar
to the way a property is declared:

Here it is explicitly stated that when handlers are added to the event,
they should be added to the delegate _myEvent, and likewise for removal.
When the event is *raised*, however, I don't understand where this tells
the application to look at _myEvent for the handler(s) to be called.

Well, you have to write the code to raise the event yourself. If you
try to call MyEvent() it will fail to compile - you need to call
myEvent().
The add and remove accessors both correspond to the set accessor in a
property declaration, but there isn't anything that corresponds to the
get accessor.

And that's deliberate - clients of the class don't get to find out
what's subscribed - they just get to subscribe and unsubscribe. Only
the class implementing the event knows how to raise it.

See http://pobox.com/~skeet/csharp/events.html for more info.
 
H

Harlan Messinger

Jon said:
Well, you have to write the code to raise the event yourself. If you
try to call MyEvent() it will fail to compile - you need to call
myEvent().

Doh! That's the piece that eluded me. And that, I think, answers the
question that led me to this one: I thought it was rather dumb (though I
suppose there must be some, probably security-related, reason) and
certainly annoying that a "real" event's delegate is private rather than
protected, preventing a derived class from raising an event declared in
a base class. Yes, I know it's a field, therefore ordinarily private,
but this seemed like a good reason to have an exception.
And that's deliberate - clients of the class don't get to find out
what's subscribed - they just get to subscribe and unsubscribe. Only
the class implementing the event knows how to raise it.

See http://pobox.com/~skeet/csharp/events.html for more info.

Heh, that redirects to the page I took my code sample from (except that
I removed the initial underscores).
 
J

Jon Skeet [C# MVP]

Harlan Messinger said:
Doh! That's the piece that eluded me. And that, I think, answers the
question that led me to this one: I thought it was rather dumb (though I
suppose there must be some, probably security-related, reason) and
certainly annoying that a "real" event's delegate is private rather than
protected, preventing a derived class from raising an event declared in
a base class. Yes, I know it's a field, therefore ordinarily private,
but this seemed like a good reason to have an exception.

Well, you can certainly make it protected if you really want to. It's
just a normal field. Personally, I wouldn't - I'd create a protected
method called OnMyEvent() which raises the event, and keep the detail
private.

Then again, I don't often have events in classes which other classes
derive from - I use inheritance fairly sparingly.
 
H

Harlan Messinger

Jon said:
Well, you can certainly make it protected if you really want to. It's
just a normal field. Personally, I wouldn't - I'd create a protected
method called OnMyEvent() which raises the event, and keep the detail
private.

It's just that I'm tired already at having to define so many things for
each event. The corresponding handler, the corresponding delegate, the
corresponding event arguments--and now, yet another entity so that I can
define an Event in an abstract base class for each derived class to
raise when appropriate.

I really don't see why events declared in the usual way couldn't have
been set up for raising by derived classes, despite their encapsulation
of the private delegate. I mean, that's the point of the
encapsulation--the derived class doesn't know about it or have access to
it, but should be able to use the public wrapper that relies on it, just
as a public or protected property Number can be called by a derived
class method even though it relies on a private field called number.
Then again, I don't often have events in classes which other classes
derive from - I use inheritance fairly sparingly.

Well, I've got interfaces IProcessMonitor and IMonitoredProcess designed
to work together, to generalize the concept of launching a process from
a main window and displaying a progress window that is loosely connected
to it:

public interface IMonitoredProcess
{
event ProgressUpdateHandler ProgressUpdated;
event ProcessStatusHandler ProcessStatusUpdated;
event ProcessEndedHandler Ended;
void Run();
void Cancel(object source, EventArgs e);
int ItemCount { get ; }
}

public interface IProcessMonitor
{
event EventHandler CancelRequested;

void SetCount(int count);
void ProgressUpdated(object source, ProgressUpdateEventArgs e);
void ProcessStatusUpdated(object source, ProcessStatusEventArgs e);
void ProcessEnded(object source, ProcessEndedEventArgs e);
}

I've got a Windows application that uses a single screen to collect
inputs, including a list of files, and then lets the user choose from
among several operations that can be applied file by file (comparing the
versions of each file at two different locations by date or
byte-by-byte, copying the files from one location to another). The
button click leads to a routine that creates a processing object
implementing IMonitoredProcess and a progress monitoring object and
wires their respective events and methods together before launching the
process asynchronously and showing the progress window that implements
IProcessMonitor.

I created an abstract class, AbstractFileProcessor, that implements
IMonitoredProcess and does most of the work that will always need to be
done: validation of inputs, iteration through the list of files, raising
progress events as the iteration proceeds. There is an abstract method,
ProcessOneFile. The override for this in each derived class is
responsible for the custom operation. It needs to be able to raise
ProcessStatusUpdate messages, including result messages ("file in
location 2 is newer") and error messages.

I could have a single class with multiple methods and choose one method
to call conditionally, but I preferred the architecture I'd come up with
where each button click handler creates the appropriate processing
object, after which all the invoking code is the same.

button1_OnClick(...)
LaunchProcess(new ThisDerivedClass(...));
...
button2_OnClick(...)
LaunchProcess(new ThatDerivedClass(...));
...

LaunchProcess initializes the processing object and creates and
initializes the progress window and then starts the former and shows the
latter. I figured that was the neatest approach. But with so much code
bloat wrapped around each event, it's frustrating.
 
J

Jon Skeet [C# MVP]

Harlan Messinger said:
It's just that I'm tired already at having to define so many things for
each event. The corresponding handler, the corresponding delegate, the
corresponding event arguments--and now, yet another entity so that I can
define an Event in an abstract base class for each derived class to
raise when appropriate.

I really don't see why events declared in the usual way couldn't have
been set up for raising by derived classes, despite their encapsulation
of the private delegate.

What do you mean by "the usual way"? As a field-like event? You can
still do that by explicitly providing a protected method which raises
the event... but if it *automatically* made the backing field
protected, with no way of making it private, that would be a really bad
idea.
I mean, that's the point of the
encapsulation--the derived class doesn't know about it or have access to
it, but should be able to use the public wrapper that relies on it, just
as a public or protected property Number can be called by a derived
class method even though it relies on a private field called number.

A derived class can use the event in exactly the same way as a
different class can - but it can't get at the private implementation,
just as a public or protected property prevents you from getting at the
implementation. There's no disparity here.

LaunchProcess initializes the processing object and creates and
initializes the progress window and then starts the former and shows the
latter. I figured that was the neatest approach. But with so much code
bloat wrapped around each event, it's frustrating.

I view it the other way: you're going to have nullity checking for the
event, right? So you need to write code for:

ProgressUpdateHandler handler = ProgressUpdated;
if (handler != null)
{
handler(...);
}

Now, if you encapsulate that in an protected OnProgressUpdated method,
you've got the "overhead" of declaring a method, but that nullity check
is only implemented once. If you started raising the event directly
from each derived class, you'd have the bloat of that nullity check
everywhere.

Admittedly one workaround the nullity check is to provide a no-op
handler as the initial value, so you know it'll never be null...
something like this:

public event ProgressUpdateHandler ProgressUpdated = delegate {};
 
H

Harlan Messinger

Jon said:
What do you mean by "the usual way"? As a field-like event?

Yep, that's what I meant.

public event MyHandlerType MyEventName;
You can
still do that by explicitly providing a protected method which raises
the event... but if it *automatically* made the backing field
protected, with no way of making it private, that would be a really bad
idea.

I had another thought about this. It's similar to the issue of why
separate access levels can't be given to get and set in a property (as
VB6 allows IIRC):

protected set { }
public get { }

It came to me that instead of just *add* and *remove*, events could also
have been given *raise*, with the further option of making add and
remove public while making raise protected where it would be private in
a field-like event. Yes, I realize this is all idle contemplation.
A derived class can use the event in exactly the same way as a
different class can - but it can't get at the private implementation,
just as a public or protected property prevents you from getting at the
implementation. There's no disparity here.

I think there is. By attempting to call

MyEvent(source, args);

in a derived class when MyEvent is declared publicly in the base class,
I am no more "getting at" the private implementation than I am "getting
at" the private implementation of a publicly declared property when I
assign to it:

base.Number = 3;

But I can do the latter--of course I can, that's the whole point of the
"set" accessor--while I can't do the former.
I view it the other way: you're going to have nullity checking for the
event, right? So you need to write code for:

ProgressUpdateHandler handler = ProgressUpdated;
if (handler != null)
{
handler(...);
}

Now, if you encapsulate that in an protected OnProgressUpdated method,
you've got the "overhead" of declaring a method, but that nullity check
is only implemented once. If you started raising the event directly
from each derived class, you'd have the bloat of that nullity check
everywhere.

Excellent! Though I didn't get around to it, I was going to observe this
myself yesterday afternoon in a follow-up--that it had come to mind that
I needed to be checking for null, and that doing so in just one place
was a good additional factor in favor of wrapping the firing of the
event in a protected method. I did go ahead and revise my app to follow
that approach. Thanks!
 
J

Jon Skeet [C# MVP]

I had another thought about this. It's similar to the issue of why
separate access levels can't be given to get and set in a property (as
VB6 allows IIRC):

protected set { }
public get { }

You can do that in C# 2/3:

public Foo
{
get { ... }
protected set { ... }
}
It came to me that instead of just *add* and *remove*, events could also
have been given *raise*, with the further option of making add and
remove public while making raise protected where it would be private in
a field-like event. Yes, I realize this is all idle contemplation.

I believe that in CLR terms there *is* an optional raise part, but that
C# and VB.NET don't generate that bit.
I think there is. By attempting to call

MyEvent(source, args);

in a derived class when MyEvent is declared publicly in the base class,
I am no more "getting at" the private implementation than I am "getting
at" the private implementation of a publicly declared property when I
assign to it

Yes you are - because the "raise" isn't encapsulated. Either you'd have
to have direct access to the field, or the event itself would have to
have a raise part. Now, the latter idea has merit - but I wouldn't want
implicit protected access to the field.

The field *is* the private implementation here. Access to that violates
encapsulation.
base.Number = 3;

But I can do the latter--of course I can, that's the whole point of the
"set" accessor--while I can't do the former.

Not without providing your own member to raise the event, no - but
that's easy to do anyway.

Excellent! Though I didn't get around to it, I was going to observe this
myself yesterday afternoon in a follow-up--that it had come to mind that
I needed to be checking for null, and that doing so in just one place
was a good additional factor in favor of wrapping the firing of the
event in a protected method. I did go ahead and revise my app to follow
that approach. Thanks!

Goodo :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top