.NET Forum / Languages / C# / August 2007
Using reflection to unsubscribe an event
|
|
Thread rating:  |
pedrito - 30 Aug 2007 23:35 GMT 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
Mattias Sjögren - 31 Aug 2007 04:57 GMT >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
 Signature Mattias Sjögren [C# MVP] mattias @ mvps.org http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com Please reply only to the newsgroup.
pedrito - 31 Aug 2007 13:33 GMT > >The problem is myControl.ControlVisibleChanged is private. >> [quoted text clipped - 5 lines] > > 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.
Jon Skeet [C# MVP] - 31 Aug 2007 13:37 GMT > 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
pedrito - 31 Aug 2007 13:48 GMT >> 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 [quoted text clipped - 5 lines] > > Jon Jon,
That may just do it. Thanks. I've never actually tried this before, so we'll see how it goes. Thanks.
Pete
pedrito - 31 Aug 2007 14:31 GMT >>> 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 [quoted text clipped - 12 lines] > > 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?
Jon Skeet [C# MVP] - 31 Aug 2007 14:56 GMT <snip>
> 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
pedrito - 31 Aug 2007 18:17 GMT > <snip> > [quoted text clipped - 14 lines] > > 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.
pedrito - 31 Aug 2007 18:20 GMT > <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...
Jon Skeet [C# MVP] - 31 Aug 2007 19:29 GMT > > 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 [quoted text clipped - 8 lines] > 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).
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
pedrito - 31 Aug 2007 19:49 GMT 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...
>> > 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 [quoted text clipped - 14 lines] > be okay, so long as both subscription and unsubscription were done in > the same manner (i.e. both using the proxy).
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|