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

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

我看到 ThreadException 是 System.Windows.Forms.Application class 上的 public 静态事件。

通常情况下,如果我使用反射从某个对象中删除事件处理程序(例如清除匿名处理程序),我会这样做(稍微精简一点):

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 public 静态事件。

在 Application 类型上有一个名为 "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" 只是使用错误的字段,但我没有看到任何其他看起来不错的候选字段。关于我如何着手做这件事有什么想法吗?

如何完成您的要求

事件旨在包装委托并向调用者隐藏调用列表。所以这很难是有原因的。

此代码是一种解决方法。我不推荐它——它很容易与更新版本的 CLR 中断。但它现在似乎有效,如果这对你很重要。

首先,看一下 source code for Application. You'll find that the thread exceptions event has a custom event accessor,它将事件处理程序存储在另一个嵌套的私有 class 中,称为 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;
}

现在,如果您再次查看代码,您可能会注意到处理程序实际上是 分配的 ,而不是添加的。换句话说:只能有一个handler。没错!!!即使您使用 += 设置它,它也会使用 = 存储在 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) => {};

然而,这将抑制默认处理程序行为,即显示对话框。