Search code examples
c#event-handlingsystem.reflection

C# - Clearing All Event Handlers for System.Windows.Forms.Application.ThreadException


I see that the ThreadException is a public static event on the System.Windows.Forms.Application class.

Normally if I were using reflection to remove an event handler (e.g. clearing an anonymous handler) from some object, I do something like this (stripped down a bit):

EventInfo ei = obj.GetType().GetEvent("FooEvent");
FieldInfo fi = obj.GetType().GetField("FooEvent", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
Delegate del = (Delegate)fi.GetValue(obj)
foreach(Delegate d in del.GetInvocationList())
{
  ei.RemoveEventHandler(obj, d);
}

But this approach doesn't seem to work for the System.Windows.Forms.Application.ThreadException public static event.

There's a field called "eventHandlers" on the Application type, but it doesn't seem to have any value when I call GetValue(null) from that FieldInfo object, and I know for certain that there's a handler attached because I added one immediately before calling the test:

System.Windows.Forms.Application.ThreadException += ...my test handler...;

Type t = typeof(System.Windows.Forms.Application);
EventInfo ei = t.GetEvent("ThreadException");
FieldInfo fi = t.GetField("eventHandlers", BindingFlags.NonPublic | BindingFlags.Static);
object test = fi.GetValue(null); // Results in null

...so I assume "eventHandlers" is simply the wrong field to use, but I don't see any other ones that look like good candidates. Any ideas about how I can go about doing this?


Solution

  • How to do what you're asking

    Events are designed to wrap the delegate and conceal the invocation list from the caller. So there is a reason this is difficult.

    This code is a workaround. I don't recommend it-- it could easily break with a newer version of the CLR. But it seems to work right now, if this is important to you.

    First, take a look at the source code for Application. You'll find that the thread exceptions event has a custom event accessor that stores the event handlers in a different, nested, private class called ThreadContext. Here is a snip:

    //From .NET reference source 
    public static event ThreadExceptionEventHandler ThreadException
    {
        add 
        {
            Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "AffectThreadBehavior Demanded");
            IntSecurity.AffectThreadBehavior.Demand();
    
            ThreadContext current = ThreadContext.FromCurrent();
            lock(current)
            {                    
                current.threadExceptionHandler = value;  //Here is where it gets stored
            }
        }
    

    To do what you're asking, we need to get ahold of that instance, which we can do with this tricky code:

    static private Type ApplicationType => typeof(Application);
    static private Type ThreadContextType => ApplicationType.GetNestedType("ThreadContext", BindingFlags.NonPublic);
    
    static private object GetCurrentThreadContext()
    {
        var threadContextCurrentMethod = ThreadContextType.GetMethod("FromCurrent", BindingFlags.Static | BindingFlags.NonPublic);
        var threadContext = threadContextCurrentMethod.Invoke(null, new object[] { });
        return threadContext;
    }
    

    Now if you look at the code again, you may notice that the handler is actually assigned, not added. In other word: there can only be one handler. That's right!!! Even though you set it using +=, it is stored in the ThreadContext using =... so it will replace any previous handler.

    So if you want to see what method is being referenced, you can retrieve it with

        static private ThreadExceptionEventHandler GetCurrentThreadExceptionEventHandler()
        {
            var threadContext = GetCurrentThreadContext();
            var threadExceptionHandler = ThreadContextType.GetField("threadExceptionHandler", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(threadContext) as System.Threading.ThreadExceptionEventHandler;
            return threadExceptionHandler;
        }
    

    And then we can remove it with

    Application.ThreadException -= GetCurrentThreadExceptionEventHandler();
    

    A complete solution looks like this:

    static private Type ApplicationType => typeof(Application);
    static private Type ThreadContextType => ApplicationType.GetNestedType("ThreadContext", BindingFlags.NonPublic);
    
    static private object GetCurrentThreadContext()
    {
        var threadContextCurrentMethod = ThreadContextType.GetMethod("FromCurrent", BindingFlags.Static | BindingFlags.NonPublic);
        var threadContext = threadContextCurrentMethod.Invoke(null, new object[] { });
        return threadContext;
    }
    
    static private void RemoveThreadExceptionEventHandler()
    {
        var threadContext = GetCurrentThreadContext();
        var threadExceptionHandler = ThreadContextType.GetField("threadExceptionHandler", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(threadContext) as System.Threading.ThreadExceptionEventHandler;
        Application.ThreadException -= threadExceptionHandler;
    }
    

    Something else that may work just as well

    If you just don't want any handler to fire, you might be fine just replacing it (since there is only one) with an empty method:

    Application.ThreadException += (s,e) => {};
    

    However this will suppress the default handler behavior, which is to show a dialog.