Using reflection to unsubscribe an event

P

pedrito

I have a library I'm using that has a lapsed listener issue. I figured I'd
just sneak in and do the unsubscription myself since I don't expect it to be
fixed any time soon.

So, I have the control whose event its subscribed to and it's subscribed to
the VisibleChanged event and has an event handler called
ControlVisibleChanged.

So basically, what I want to do is:

myControl.VisibleChanged -= new
EventHandler(myControl.ControlVisibleChanged);

The problem is myControl.ControlVisibleChanged is private.

So, what do I pass to the EventHandler constructor?

Thanks
 
M

Mattias Sjögren

The problem is myControl.ControlVisibleChanged is private.
So, what do I pass to the EventHandler constructor?

If you hav the right permissions you can get a MethodInfo for a
private method with Reflection as well. Just make sure you include
BindingFlags.NonPublic when retrieving it.


Mattias
 
P

pedrito

Mattias Sjögren said:
If you hav the right permissions you can get a MethodInfo for a
private method with Reflection as well. Just make sure you include
BindingFlags.NonPublic when retrieving it.


Mattias

I can get the MethodInfo, but I can't pass the MethodInfo to new
EventHandler() because it doesn't take a method info. It normally takes the
method itself and I certainly need to pass the class instance associated
with the method, somehow.
 
J

Jon Skeet [C# MVP]

I can get the MethodInfo, but I can't pass the MethodInfo to new
EventHandler() because it doesn't take a method info. It normally takes the
method itself and I certainly need to pass the class instance associated
with the method, somehow.

Is Delegate.CreateDelegate what you're after?

Jon
 
P

pedrito

Jon Skeet said:
Is Delegate.CreateDelegate what you're after?

Jon

Jon,

That may just do it. Thanks. I've never actually tried this before, so we'll
see how it goes. Thanks.

Pete
 
P

pedrito

pedrito said:
Jon,

That may just do it. Thanks. I've never actually tried this before, so
we'll see how it goes. Thanks.

Pete

Jon,

Not having any luck. This is what I did:

parentControl.VisibleChanged -=
Delegate.CreateDelegate(typeof(EventHandler), myControl,
"ControlVisibleChanged") as EventHandler;

ControlVisibleChanged is the event handler. myControl is the control that
subscribed to parentControl.VisibleChanged.

When I do this, I get an ArgumentException with the message, "Error binding
to target method", which I can only assume is ControlVisibleChanged because
it's private.

I'm using .NET 1.1, so I don't get the overrides of CreateDelegate that take
a MethodInfo and an object instance, not that it looks like it would matter.
It appears that the exception being thrown is coming from an internal
implementation given that the callstack is:

System.ArgumentException: Error binding to target method.
at System.Delegate.InternalCreate(Object target, String method, Boolean
ignoreCase)
at System.Delegate.CreateDelegate(Type type, Object target, String
method)

and

[MethodImpl(MethodImplOptions.InternalCall)]
internal extern void InternalCreate(object target, string method, bool
ignoreCase);

Any idea on how I might around this?
 
J

Jon Skeet [C# MVP]

On Aug 31, 2:31 pm, "pedrito" <pixbypedrito at yahoo.com> wrote:

I'm using .NET 1.1, so I don't get the overrides of CreateDelegate that take
a MethodInfo and an object instance, not that it looks like it would matter.

No, I think that's absolutely what matters. I don't see any reason why
it shouldn't work if you can create the delegate. Unfortunately,
without the right overloads, I'm not sure of the best way of doing
this.

What you *could* do is create a class to encapsulate the method info
and target, and expose a method which has the appropriate signature to
be used as an event handler - that could then call MethodInfo.Invoke.
It would be relatively slow, but it should work.

Jon
 
P

pedrito

Jon Skeet said:
On Aug 31, 2:31 pm, "pedrito" <pixbypedrito at yahoo.com> wrote:



No, I think that's absolutely what matters. I don't see any reason why
it shouldn't work if you can create the delegate. Unfortunately,
without the right overloads, I'm not sure of the best way of doing
this.

What you *could* do is create a class to encapsulate the method info
and target, and expose a method which has the appropriate signature to
be used as an event handler - that could then call MethodInfo.Invoke.
It would be relatively slow, but it should work.

Jon

Jon,

You're right. The "Not that it matters" was wrong because the problem is
that it's looking for a public reference.

Here's what I did and I should probably be thrown in jail for doing this,
but it almost seems to have worked.

MethodInfo ovc = typeof(MyControlClass).GetMethod("OnVisibleChanged",
BindingFlags.NonPublic | BindingFlags.Instance);
Delegate ovcDelegate = Delegate.CreateDelegate(typeof(EventHandler), this,
"VScrollBarValueChanged") as EventHandler;

FieldInfo fi = typeof(Delegate).GetField("_target", BindingFlags.NonPublic |
BindingFlags.Instance);
fi.SetValue(ovcDelegate, myControl);
fi = typeof(Delegate).GetField("_method", BindingFlags.NonPublic |
BindingFlags.Instance);
fi.SetValue(ovcDelegate, ovc);
fi = typeof(Delegate).GetField("_methodPtr", BindingFlags.NonPublic |
BindingFlags.Instance);
fi.SetValue(ovcDelegate, ovc.MethodHandle.Value);
fi = typeof(Delegate).GetField("_methodPtrAux", BindingFlags.NonPublic |
BindingFlags.Instance);
fi.SetValue(ovcDelegate, ovc.MethodHandle.Value);
parentControl.VisibleChanged -= ovcDelegate as EventHandler;

Basically, what I did was I created a dummy delegate that had the same
signature (though I don't think that really matters).

Then, using reflection, I filled in the all the fields of the Delegate with
the information for the event handler I really wanted a delegate for.

Now, as I said, this "almost" worked.

It did appear to successfully remove the old reference. But somehow, it
created an entirely new reference with a call stack leading back to

parentControl.VisibleChanged -= ovcDelegate as EventHandler;

This is according to .NET profiler... The relevant portion of the call stack
is:

MulticastDelegate.RemoveImpl(Delegate)
Delegate.Remove(Delegate, Delegate)
EventHandlerList.RemoveHandler(object, Delegate)
Control.remove_VisibleChanged(EventHandler)

How does that create a reference? Unless it's walking the stack to see where
the call is coming from and saying, "Aha, this is a different class trying
to unsubscribe the original subscription, so let's keep a reference to the
new one..." I don't know. My head is about to pop.
 
P

pedrito

Jon Skeet said:
On Aug 31, 2:31 pm, "pedrito" <pixbypedrito at yahoo.com> wrote:

<snip>
What you *could* do is create a class to encapsulate the method info
and target, and expose a method which has the appropriate signature to
be used as an event handler - that could then call MethodInfo.Invoke.
It would be relatively slow, but it should work.

In reference to this, I'm not sure I understand what you mean. Doesn't the
unsubscription need to have the same underlying function pointer (it appears
that's technically how it's happening underneath with the
Delegate._methodPtr)...

Unless I'm misunderstanding, your method would unsubscribe a different
_methodPtr...
 
J

Jon Skeet [C# MVP]

In reference to this, I'm not sure I understand what you mean. Doesn't the
unsubscription need to have the same underlying function pointer (it appears
that's technically how it's happening underneath with the
Delegate._methodPtr)...

Unless I'm misunderstanding, your method would unsubscribe a different
_methodPtr...

You'd have to override Equals in your "extra" class, but then it should
be okay, so long as both subscription and unsubscription were done in
the same manner (i.e. both using the proxy).
 
P

pedrito

Ahh, see, that wouldn't work. I don't do the subscription. The library I'm
using does the subscription. It just never unsubscribes under any condition,
otherwise I'd find whatever method it uses to unsubscribe and call that. But
since it doesn't, I'm trying to figure out a way to force the
unsubscription. The lapsed listener is hanging onto a reference of my
control and I can't get rid of it...
 

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