C # - очистка всех обработчиков событий для System.Windows.Forms.Application.ThreadException - PullRequest
0 голосов
/ 06 октября 2018

Я вижу, что 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", но оно, кажется, не имеет никакого значения, когда я вызываю GetValue (null) из этого объекта FieldInfo, и я точно знаю, что к нему добавлен обработчик, потому что я добавилодин непосредственно перед вызовом теста:

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 голосов
/ 06 октября 2018

Как сделать то, что вы спрашиваете

События предназначены для переноса делегата и сокрытия списка вызовов от вызывающей стороны.Поэтому есть причина, по которой это сложно.

Этот код является обходным путем.Я не рекомендую это - это могло легко сломаться с более новой версией CLR.Но, похоже, это работает прямо сейчас, если это важно для вас.

Сначала взгляните на исходный код для приложения.Вы обнаружите, что событие исключений потока имеет пользовательский метод доступа к событию , который хранит обработчики событий в другом, вложенном, закрытом классе, называемом 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) => {};

Однако это подавит поведение обработчика по умолчанию, то есть отображение диалога.

...