C#-清除System.Windows.Forms.Application.ThreadException的所有事件处理程序

时间:2018-10-06 00:58:38

标签: c# event-handling system.reflection

我看到ThreadException是System.Windows.Forms.Application类上的一个公共静态事件。

通常,如果我使用反射从某个对象中删除事件处理程序(例如,清除匿名处理程序),我将执行以下操作(精简一点):

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);
}

但是这种方法似乎不适用于System.Windows.Forms.Application.ThreadException公共静态事件。

“应用程序”类型上有一个名为“ eventHandlers”的字段,但是当我从该FieldInfo对象中调用GetValue(null)时,它似乎没有任何值,而且我肯定知道有附加的处理程序,因为我添加了紧接测试前一个:

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

...所以我假设“ eventHandlers”只是使用错误的字段,但是我看不到其他任何看起来像是好的候选对象。关于如何执行此操作的任何想法?

1 个答案:

答案 0 :(得分:1)

如何处理您要问的问题

事件旨在包装委托并在调用者中隐藏调用列表。因此有一个困难的原因。

此代码是一种解决方法。我不建议这样做,因为它可能很容易被更新版本的CLR破坏。但是,如果这对您很重要,它现在似乎可以正常工作。

首先,看一下source code for Application。您会发现线程异常事件有一个custom event accessor,该事件将事件处理程序存储在另一个名为ThreadContext的嵌套私有类中。这是一个片段:

//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
        }
    }

要执行您要的操作,我们需要获取该实例,我们可以使用以下棘手的代码来做到这一点:

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

现在,如果再次查看代码,您可能会注意到该处理程序实际上是分配的,没有添加。换句话说:只能有一个处理程序。是的!!!即使您使用+=进行了设置,它仍然使用=存储在ThreadContext中,因此它将替换以前的任何处理程序。

因此,如果您想查看所引用的方法,可以使用

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

然后我们可以使用

将其删除
Application.ThreadException -= GetCurrentThreadExceptionEventHandler();

完整的解决方案如下:

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

其他可能同样有效的方法

如果您不希望触发任何处理程序,则可以使用空方法替换(因为只有一个)而已:

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

但是,这将抑制默认的处理程序行为,即显示一个对话框。