Clearing Events without -=

  • Thread starter Thread starter Michael Kennedy [UB]
  • Start date Start date
M

Michael Kennedy [UB]

Hi,

I have a situation where I need to clear the event sinks from an event
inside a custom class. But I don't know which methods signed up for that
event.

Consider the following example:

public class B
{
public event System.EventHandler DoneIt;

// ....
}

B b = new B();
b.DoneIt += X;

Now eventually suppose I want to "reset" b so that DoneIt == null but I
don't want to completely generate a new object, just the event sinks.
Typically this is done using

b.DoneIt -= X;

But in this case we don't know what X is. B is in a class library and we
have no control or knowedge of what the consumers of that class might do
with DoneIt.

My question is: Is there a way to have b force DoneIt to be null without
explicitly knowing the right hand side of += since it happened outside our
code?

Thanks in advance,
Michael

ps In case you haven't tried this before: b.DoneIt = null; does not compile.
 
Michael Kennedy said:
I have a situation where I need to clear the event sinks from an event
inside a custom class. But I don't know which methods signed up for that
event.

If you are not the "owner" of the custom class, you can't do this.
Within the custom class you can clear the event, but if the custom
class doesn't make any means of clearing the event available to you, it
presumably doesn't want you to be able to do so.
 
Using reflection, you can do b.GetType().GetEvents() or
b.GetType().GetEvent() to find the particular event of interest and use the
EventInfo.RemoveEventHandler() to remove it.

I haven't done this, but a quick look at the docs would lead me to start
here.

Pete
 
ps In case you haven't tried this before: b.DoneIt = null; does not compile.

Thats because you used

public event System.EventHandler DoneIt;

"event" protects it from that sort of "abuse". If you change it to

public System.EventHandler DoneIt;

the compilation error will go away - however now you have a publically
accessible field that clients of the object can abuse without the knowledge
of the object - a situation that should be avoided. Simply create a public method
in class B to clear the event sink list, e.g.:

public void ClearDoneIt(){
// Place any Checks here to determine
// it you want to follow the "suggestion"
// to clear DoneIt
DoneIt = null;
} // End method B.RaiseDoneIt

that way you won't have to leave the event sink list so exposed.

Source Code follows:

#define ClearEvent
using System;

namespace ClearEvent {

class TestDriver {
static void Main(string[] args) {

B b = new B();
DoneItSubscriber s1 = new DoneItSubscriber( "First", b );
DoneItSubscriber s2 = new DoneItSubscriber( "Second", b );
Console.WriteLine( "Raising DoneIt Event." );
b.RaiseDoneIt();
Console.WriteLine( "Clearing DoneIt Event sinks." );
#if ClearEvent
b.ClearDoneIt();
#else
b.DoneIt = null;
#endif
Console.WriteLine( "Raising DoneIt Event again." );
b.RaiseDoneIt();
}
}

public class B {

#if ClearEvent
public event System.EventHandler DoneIt;
#else
public System.EventHandler DoneIt;
#endif

public void RaiseDoneIt(){
OnDoneIt( EventArgs.Empty );
} // End method B.RaiseDoneIt

public void ClearDoneIt(){
// Place any Checks here to determine
// it you want to follow the "suggestion"
// to clear DoneIt
DoneIt = null;
} // End method B.RaiseDoneIt

// "Publishing Events Defensively" p.108
// "Programming .NET Components" by Juval Löwry, April 2003 O'Reilly & Associates Inc
// ISBN 0596003471
protected void OnDoneIt( EventArgs e ){
if (null == DoneIt ) return;

Delegate[] delegates = DoneIt.GetInvocationList();
foreach( Delegate del in delegates ){
EventHandler sink = (EventHandler)del;
try {
sink( this, e );

} catch {
Console.WriteLine( "OnDoneIt: Eventhandler raised exception." );
}
}
} // End method B.OnDoneIt

} // end class B

public class DoneItSubscriber {
private string name_;

public DoneItSubscriber( string name, B eventSource ) {
name_ = name;
eventSource.DoneIt += new EventHandler( HandleDoneIt );
}

public void HandleDoneIt( object sender, EventArgs e ){
Console.WriteLine(
name_ + ": handled DoneIt."
);
}
} // end class DoneItSubscriber

}
 
Hi Pete,

That's an interesting idea. Although working with the typeof(B) alone it
seems like it is not possible to effect instance level events. Maybe it
could effect something with the static events, but I will definitely have a
look.

Thanks!
Michael
 
Hi,

That is a good idea. I'll see how this idea fits into my architecture. It
looks like it could be a winner.

Thanks!
Michael


UAError said:
ps In case you haven't tried this before: b.DoneIt = null; does not
compile.

Thats because you used

public event System.EventHandler DoneIt;

"event" protects it from that sort of "abuse". If you change it to

public System.EventHandler DoneIt;

the compilation error will go away - however now you have a publically
accessible field that clients of the object can abuse without the knowledge
of the object - a situation that should be avoided. Simply create a public method
in class B to clear the event sink list, e.g.:

public void ClearDoneIt(){
// Place any Checks here to determine
// it you want to follow the "suggestion"
// to clear DoneIt
DoneIt = null;
} // End method B.RaiseDoneIt

that way you won't have to leave the event sink list so exposed.

Source Code follows:

#define ClearEvent
using System;

namespace ClearEvent {

class TestDriver {
static void Main(string[] args) {

B b = new B();
DoneItSubscriber s1 = new DoneItSubscriber( "First", b );
DoneItSubscriber s2 = new DoneItSubscriber( "Second", b );
Console.WriteLine( "Raising DoneIt Event." );
b.RaiseDoneIt();
Console.WriteLine( "Clearing DoneIt Event sinks." );
#if ClearEvent
b.ClearDoneIt();
#else
b.DoneIt = null;
#endif
Console.WriteLine( "Raising DoneIt Event again." );
b.RaiseDoneIt();
}
}

public class B {

#if ClearEvent
public event System.EventHandler DoneIt;
#else
public System.EventHandler DoneIt;
#endif

public void RaiseDoneIt(){
OnDoneIt( EventArgs.Empty );
} // End method B.RaiseDoneIt

public void ClearDoneIt(){
// Place any Checks here to determine
// it you want to follow the "suggestion"
// to clear DoneIt
DoneIt = null;
} // End method B.RaiseDoneIt

// "Publishing Events Defensively" p.108
// "Programming .NET Components" by Juval Löwry, April 2003 O'Reilly & Associates Inc
// ISBN 0596003471
protected void OnDoneIt( EventArgs e ){
if (null == DoneIt ) return;

Delegate[] delegates = DoneIt.GetInvocationList();
foreach( Delegate del in delegates ){
EventHandler sink = (EventHandler)del;
try {
sink( this, e );

} catch {
Console.WriteLine( "OnDoneIt: Eventhandler raised exception." );
}
}
} // End method B.OnDoneIt

} // end class B

public class DoneItSubscriber {
private string name_;

public DoneItSubscriber( string name, B eventSource ) {
name_ = name;
eventSource.DoneIt += new EventHandler( HandleDoneIt );
}

public void HandleDoneIt( object sender, EventArgs e ){
Console.WriteLine(
name_ + ": handled DoneIt."
);
}
} // end class DoneItSubscriber

}
 
Hi Jon,

In the situation I outlined, I was the owner of the custom class. But I can
see how if you had a public event and it was "nullable" as I was asking for,
then other classes could mess-up the events by nulling them since it is
public and would be accessable in that way to everyone.

Thanks!
Michael
 
Hi Michael,

Thanks for posting. I think the EventInfo.RemoveEventHandler() method can
affect instance level events:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/
frlrfsystemreflectioneventinfoclassremoveeventhandlertopic.asp

As we can see, the first parameter of the method is "object target". It is
the instance that the method operates on to remove the delegate from.

On the other hand, the EventInfo.RemoveEventHandler() method cannot achieve
your goal. The reason is similar to the "-=" operator. For the method to
operate, it needs a second parameter to identify the delegate to remove. If
we have no reference to the delegate, we cannot remove it.

I hope this makes sense.

Regards,

Felix Wang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
Hi Jon,

I think that we can achieve the goal with Reflection, even if we are not
the "owner" of the custom class. The following is a simple C# Console
Application for demonstraton:

using System;
using System.Reflection;

public delegate void MyDelegate();

public class B
{
public event MyDelegate AEvent;
public void RaiseAEvent()
{
if (AEvent != null)
{
AEvent();
} else Console.WriteLine("Event delegate is null");
}
}

public class ClassForMain
{
public static void Main(string[] args)
{
B b = new B();
b.AEvent += new MyDelegate(Hello);
b.RaiseAEvent();

Type t = typeof(B);
FieldInfo f = t.GetField("AEvent", BindingFlags.Instance |
BindingFlags.NonPublic);
f.SetValue(b,null);
b.RaiseAEvent();
}

public static void Hello()
{
Console.WriteLine("Hello");
}
}

Regards,

Felix Wang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
Felix Wang said:
I think that we can achieve the goal with Reflection, even if we are not
the "owner" of the custom class. The following is a simple C# Console
Application for demonstraton:

<snip>

Your code is assuming that:

a) You have sufficient access to modify private fields in the relevant
class
b) The event is defined in such a way that there's a field of the same
name

Neither need be true.
 
Hi Felix,

Yeah, that makes sense. The console app you posted above this message in
this thread is very interesting.

Thank you for the advice,
Michael
 
Hi Jon,

Thanks for your reply. I agree with your point (a). However, regarding the
point (b), when we declare a public event in C#, by default, a private
field with type Delegate (MulticastDelegate) will be added by the compiler.
The private delegate field will share the same name as the event normally.

Regards,

Felix Wang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
Felix Wang said:
Thanks for your reply. I agree with your point (a). However, regarding the
point (b), when we declare a public event in C#, by default, a private
field with type Delegate (MulticastDelegate) will be added by the compiler.
The private delegate field will share the same name as the event normally.

That's how they're added by default, yes - but it's not how they *have*
to be added. Indeed, many controls *don't* use that mechanism as if you
have a lot of events, only some of which may have any real handlers, it
wastes a lot of memory. You can explicitly specify your own add/remove
parts of the event, in which case the compiler doesn't add any fields
for you automatically.

See section 17.7 of the C# ECMA spec for more details. An example of
not using a single field per event is given in 17.7.2.
 
Hi Jon,

Thanks for your response. I am aware of the usage of EventHandlerList class
and the optimization. We may also use HashTable to store the delegates. It
is my pleasure to discuss these with you.

Regards,

Felix Wang
Microsoft Online Partner Support
Get Secure! - www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
Back
Top