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?
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;
}
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.