Event Subscription. Why?

B

bob

Hi,
maybe I am coding it wrong,
but declare an event in ClassA ( in dll) which I only want to
subscribe to in ClassB client Assy).

If I don't subscribe to it in ClassA then it fails with a null
exception when I try to raise it.
So I make a dummy routine that subscribes to it in ClassA and away we
go.

namespace myDllnamespace
{
public void delegate MySpecialEventhandler(object
sender,MySpecialEventArgs e);

public Interface MyInterface
{
event MySpecialEventhandler MyEvent;
void DoSomeStuff();
}

ClassA:MyInterface
{
public event MySpecialEventhandler MyEvent;
public ClassA()
{
MyEvent+=LocalSubscription;
}

public void DoSomeStuff()
{
LetsRaiseTheEvent();
}

private void LetsRaisetheEvent();
{
MySpecialEventArgs e = new MySpecialEventArgs {SpecialPayload =
something};
MyEvent(this,e); //***NULL EXCEPTION if local subscription is not
attended to.
}

private void LocalSubscription(object sender,MySpecialEventArgs e)
{
//Empty method but can't do with out it.
}
}
}

If my code is correct then there is a linkage between event raising
and event 'subscription'.
Haven't seen any mention of it in the documentation so I guess it is
just my methodology.
Is there a better way to do this? (Raise event in dll and consume in
client assy)
thanks
Bob
 
F

Family Tree Mike

bob said:
Hi,
maybe I am coding it wrong,
but declare an event in ClassA ( in dll) which I only want to
subscribe to in ClassB client Assy).

If I don't subscribe to it in ClassA then it fails with a null
exception when I try to raise it.
So I make a dummy routine that subscribes to it in ClassA and away we
go.

namespace myDllnamespace
{
public void delegate MySpecialEventhandler(object
sender,MySpecialEventArgs e);

public Interface MyInterface
{
event MySpecialEventhandler MyEvent;
void DoSomeStuff();
}

ClassA:MyInterface
{
public event MySpecialEventhandler MyEvent;
public ClassA()
{
MyEvent+=LocalSubscription;
}

public void DoSomeStuff()
{
LetsRaiseTheEvent();
}

private void LetsRaisetheEvent();
{
MySpecialEventArgs e = new MySpecialEventArgs {SpecialPayload =
something};
MyEvent(this,e); //***NULL EXCEPTION if local subscription is not
attended to.
}

private void LocalSubscription(object sender,MySpecialEventArgs e)
{
//Empty method but can't do with out it.
}
}
}

If my code is correct then there is a linkage between event raising
and event 'subscription'.
Haven't seen any mention of it in the documentation so I guess it is
just my methodology.
Is there a better way to do this? (Raise event in dll and consume in
client assy)
thanks
Bob


You need to check if the event is subscribed to before raising the event.

This link provides an example: (look at protected virtual void
OnAlarm(AlarmEventArgs e))

http://msdn.microsoft.com/en-us/library/9aackb16.aspx
 
P

Pavel Minaev

maybe I am coding it wrong,
but declare an event in ClassA ( in dll) which I only want to
subscribe to in ClassB client Assy).

If I don't subscribe to it in ClassA then it fails with a null
exception  when I try to raise it.

An event which has no subscribers is null. Therefore, before raising
the event, you should check it for null. To do so in a thread-safe
manner, first copy the value to a local value and then check & invoke
that.
So I make a dummy routine that subscribes to it in ClassA and away we
go.

Alternatively, you can use anonymous delegate to initialize the event:

event MySpecialEventhandler MyEvent = delegate {};
 
M

Morten Wennevik [C# MVP]

Hi Bob,

When a class declares an event, it also keeps track of its subscribers by
using a hidden list. The following example of a regular event implementation
can be written as the second example. Internally a list of delegates is kept
and this list is not initialized until at least one subscriber is added due
to memory conservation reasons. This is why you need to check for null
before raising an event.

public class SomeClass
{
MyClass myClass;
public SomeClass()
{
myClass = new MyClass();
myClass.MyEvent += EventMethod;
myClass.RaiseEvent();
myClass.MyEvent -= EventMethod;
}

void EventMethod(object sender, MyEventArgs e)
{
string s = e.Text;
}
}

public class MyEventArgs : EventArgs
{
public string Text { get; set; }
}

public class MyClass
{
public event EventHandler<MyEventArgs> MyEvent;

public void RaiseEvent()
{
if (MyEvent != null)
{
MyEvent(this, new MyEventArgs { Text = "Hello World" });
}
}
}

The above example translates more or less to the below. Note that += and -=
actually just calls add_Event and remove_Event methods, and these methods
updates a subscriber list.

public class SomeClass
{
MyClass myClass;
public SomeClass()
{
myClass = new MyClass();
myClass.add_MyEvent(EventMethod);
myClass.RaiseEvent();
myClass.remove_MyEvent(EventMethod);
}

void EventMethod(object sender, MyEventArgs e)
{
string s = e.Text;
}
}

public class MyEventArgs : EventArgs
{
public string Text { get; set; }
}

public class MyClass
{
private List<EventHandler<MyEventArgs>> subscribers = null;

public void add_MyEvent(EventHandler<MyEventArgs> subscriber)
{
if (subscribers == null)
subscribers = new List<EventHandler<MyEventArgs>>();
subscribers.Add(subscriber);
}

public void remove_MyEvent(EventHandler<MyEventArgs> subscriber)
{
if (subscribers == null)
return;
subscribers.Remove(subscriber);
if(subscribers.Count == 0)
subscribers = null;
}

public void RaiseEvent()
{
if (subscribers != null)
{
foreach (EventHandler<MyEventArgs> subscriber in subscribers)
subscriber.Invoke(this, new MyEventArgs { Text = "Hello
World" });
}
}
}
 
M

Morten Wennevik [C# MVP]

I didn't intend to show the exact translation although I probably should
have made that clearer.

Actually the code translates to

[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MyEvent(EventHandler<MyEventArgs> value)
{
this.MyEvent = (EventHandler<MyEventArgs>)
Delegate.Combine(this.MyEvent, value);
}

[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MyEvent(EventHandler<MyEventArgs> value)
{
this.MyEvent = (EventHandler<MyEventArgs>) Delegate.Remove(this.MyEvent,
value);
}

Combine internally works with an object[] which is a list although not a
List. Each time you add or remove a subscriber you will create a new list so
each call to MyEvent will only work with the current list. In that regards
just using add/remove of a List<EventHandler> may give an incorrect
understanding of events.

As for creating a reference before checking for null, that is relevant only
if you unsubscribe from another thread, and may actually cause the event to
notify a subscriber that is no longer subscribing, or even worse, no longer
exists. What the best solution is depends on the implementation. If you
tend to delete the subscriber after unsubscibing it may be safer not creating
the reference. If on the other hand you do lots of unsubscribing but do not
delete the subscriber, it may be safer creating a reference and risk
notifying the subscriber after he unsubscribed. In both cases a subscriber
may unsubscribe after you have started notifying the subscribers, in which
case he will be notified after he unsubscribed anyway.

--
Happy Coding!
Morten Wennevik [C# MVP]


Peter Duniho said:
[...]
public class MyClass
{
public event EventHandler<MyEventArgs> MyEvent;

public void RaiseEvent()
{
if (MyEvent != null)
{
MyEvent(this, new MyEventArgs { Text = "Hello World" });
}
}
}

The above example translates more or less to the below.

But mostly "less". ;)

As with properties in C# 3.0, events have an implicit implementation if
you don't provide one; but you can always provide an explicit
implementation if you want.

In the implicit implementation, there's no "list" per se, except inasmuch
as a MulticastDelegate instance encapsulates one. The true equivalent of
the above code is this:

public class MyClass
{
private EventHandler<MyEventArgs> _MyEventValue;
public event EventHandler<MyEventArgs> MyEvent
{
add
{
lock (this)
{
_MyEventValue += value;
}
}

remove
{
lock (this)
{
_MyEventValue -= value;
}
}
}

public void RaiseEvent()
{
if (_MyEventValue != null)
{
_MyEventValue(this, new MyEventArgs { Text = "Hello World"
});
}
}
}

With the minor caveat that the compiler's name for "_MyEventValue" is
something else entirely.

Note also that the more correct pattern for the "RaiseEvent()" method is
a) for it to not be public, and b) for thread safety, for it to assign the
current value to a local before checking for null. E.g.:

protected void RaiseEvent()
{
EventHandler<MyEventArgs> handler = _MyEventValue;

if (handler != null)
{
handler(this, new MyEventArgs { Text = "Hello World" });
}
}

Pete
 
N

not_a_commie

    this.MyEvent = Delegate.Combine(this.MyEvent, value);

This is the whole point right here. The C# event keyword just makes it
so that the compiler translates '+=' to the above combine call. Events
are simply objects like everything else in C#. If you don't initialize
your object it will be null. The compiler allows you to call 'MyEvent
()'. I wished it wouldn't. That obscures the fact that it is simply
translating that to 'MyEvent.Invoke()'. In the latter case, it's
obvious that your object is null when it's not initialized. Initialize
your events with ' = delegate {};'. That way you don't ever have to
check for null. You don't have to worry about multiple threads
interrupting your null check. The overhead of the empty function call
is negligible.
 
B

bob

Hi All,
Thank you all for your thoughtful replies.

I think my problem is that I haven't really been thinking of events as
objects.
I tend to make sure the event, the delegate and special eventarg class
are correctly constructed then 'let the magic happen'.

9 times out of 10 I use them within 'scope' so I am subscribing
'locally' and the problem never presents itself.
It is just the odd occasion when I need to notify something externally
and not also internally.

Funny thing is I had the code working for a long time, only thing I
can think of is that I was refactoring and ditched the empty
subscription routine.


Quite like Morten's idea of myevent = delegate{};
Fits closest to the simple concept of declare it, initialise it.

regards
Bob
 
M

Morten Wennevik [C# MVP]

Peter Duniho said:
[...]
Now, that...the above...is exactly the same as the code I posted.

No it isn't.

It is in the sense I specifically said it is. If you want to raise a
straw man and claim some other sense by which you want to measure, that's
fine with me. But don't think it actually matters.

It is in the sense that it does essentially do the same, but it is not
"exactly the same code". Nor does it compile to the same IL, although both
samples have the same IL core.
Really? Because your original code showed completely incorrect method
calls for the actual delegate operations.

Really! I never claimed my original code to be exactly the same, hence the
"more or less" disclaimer. My goal was to show -= and += in event handling
works with lists which may end up empty by providing a code sample to
visualize that. I apologive for not having made that clearer.
But whatever...if you want to insist your code example was accurate and
useful for understanding events, fine by me. Anyone can read the docs and
spec and judge for themselves.

No, and I never did, and it is a bad example to demonstrate how events
actually work, but given level of understanding I guessed the OP had I found
it to be adequate.
[...]
and may actually cause the event to
notify a subscriber that is no longer subscribing, or even worse, no
longer
exists.

The latter is not possible. The former is, but that's not a flaw. It's
a
completely unavoidable situation and just means you need to understand
how
events work. Subscribers cannot make the assumption that they have
successfully unsubscribed from the event, without some kind of explicit
coordination with the publisher of the event (which usually isn't
present).

Sure you can delete a subscriber.

You wrote "no longer exists". The word "delete" is itself ambiguous, but
the subscriber is an object, and the only way for it to "no longer exist"
is for it to be garbage collected.

Fair enough. As for remoting you don't actually have a reference to the
object, only a proxy representing the object, so the object may be garbage
collected and "no longer exists" even though you indirectly still hold a
reference to it :)
A subscriber that is notified through the event (which is the scenario you
gave) _cannot_ have been garbage collected. It simply won't happen.


But you can't. It's impossible. There is always a race condition that
cannot be resolved without additional synchronization (and in particular,
cooperation between the event publisher and the subscriber).

Exactly! It may blow up as a nullreference indicating your event handling
is weak, or you may get weird side effects if a subscriber gets called after
unsubscription. Generally you use events in a single gui-thread with
possibly a backgroundworker that doesn't handle subscription. In this case
just testing for null is sufficient.
[...]
To sum up:
Subscribing or unsubscribing to an event will cause a new list of
subscribers to be created. If there are no subscribers the list will
most
likely be null.

I don't understand that statement. Using the default event
implementation, or any other similar implementation (i.e. where the
subscribers are encapsulated in a MulticastDelegate), the _delegate_
instance _will_ be null if there are no subscribers (and there won't even
be a list at all, never mind a null reference for one).

Conversely, if you've got some data structure that is not null if there
are no subscribers, then you are using some explicit implementation for
the event that is completely different from the default implementation,
and thus any guidance given for the default implementation is completely
irrelevant.

Yes?
[...]
If you hold a reference to the list of subscribers it may be outdated
when
you use it. Some of the subscribers may have unsubscribed.

The subscriber is not guaranteed to be unsubscribed after he
unsubscribes,
but subscribers usually believe so.

Subscribers that make that assumption are flawed code. It's not a valid
assumption to make.

True, but they still make that assumption.
 
M

Morten Wennevik [C# MVP]

Peter Duniho said:
[...]
Now, that...the above...is exactly the same as the code I posted.

No it isn't.

It is in the sense I specifically said it is. If you want to raise a
straw man and claim some other sense by which you want to measure, that's
fine with me. But don't think it actually matters.

It is in the sense that it does essentially do the same, but it is not
"exactly the same code". Nor does it compile to the same IL, although both
samples have the same IL core.
Really? Because your original code showed completely incorrect method
calls for the actual delegate operations.

Really! I never claimed my original code to be exactly the same, hence the
"more or less" disclaimer. My goal was to show -= and += in event handling
works with lists which may end up empty by providing a code sample to
visualize that. I apologive for not having made that clearer.
But whatever...if you want to insist your code example was accurate and
useful for understanding events, fine by me. Anyone can read the docs and
spec and judge for themselves.

No, and I never did, and it is a bad example to demonstrate how events
actually work, but given level of understanding I guessed the OP had I found
it to be adequate.
[...]
and may actually cause the event to
notify a subscriber that is no longer subscribing, or even worse, no
longer
exists.

The latter is not possible. The former is, but that's not a flaw. It's
a
completely unavoidable situation and just means you need to understand
how
events work. Subscribers cannot make the assumption that they have
successfully unsubscribed from the event, without some kind of explicit
coordination with the publisher of the event (which usually isn't
present).

Sure you can delete a subscriber.

You wrote "no longer exists". The word "delete" is itself ambiguous, but
the subscriber is an object, and the only way for it to "no longer exist"
is for it to be garbage collected.

Fair enough. As for remoting you don't actually have a reference to the
object, only a proxy representing the object, so the object may be garbage
collected and "no longer exists" even though you indirectly still hold a
reference to it :)
A subscriber that is notified through the event (which is the scenario you
gave) _cannot_ have been garbage collected. It simply won't happen.


But you can't. It's impossible. There is always a race condition that
cannot be resolved without additional synchronization (and in particular,
cooperation between the event publisher and the subscriber).

Exactly! It may blow up as a nullreference indicating your event handling
is weak, or you may get weird side effects if a subscriber gets called after
unsubscription. Generally you use events in a single gui-thread with
possibly a backgroundworker that doesn't handle subscription. In this case
just testing for null is sufficient.
[...]
To sum up:
Subscribing or unsubscribing to an event will cause a new list of
subscribers to be created. If there are no subscribers the list will
most
likely be null.

I don't understand that statement. Using the default event
implementation, or any other similar implementation (i.e. where the
subscribers are encapsulated in a MulticastDelegate), the _delegate_
instance _will_ be null if there are no subscribers (and there won't even
be a list at all, never mind a null reference for one).

Conversely, if you've got some data structure that is not null if there
are no subscribers, then you are using some explicit implementation for
the event that is completely different from the default implementation,
and thus any guidance given for the default implementation is completely
irrelevant.

Yes?
[...]
If you hold a reference to the list of subscribers it may be outdated
when
you use it. Some of the subscribers may have unsubscribed.

The subscriber is not guaranteed to be unsubscribed after he
unsubscribes,
but subscribers usually believe so.

Subscribers that make that assumption are flawed code. It's not a valid
assumption to make.

True, but they still make that assumption.
 

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

Similar Threads

How can these lines be rewritten 15
About delegate and event. 1
class inherit question 4
struggling with events 4
Delegates and Events 8
When is an event null? 22
Asynchronous event 2
Raising and event 3

Top