C# Как узнать, подключено ли событие

Я хочу иметь возможность узнать, связано ли событие или нет. Я осмотрелся, но нашел только решения, связанные с изменением внутренностей объекта, содержащего событие. Я не хочу этого делать.

Вот некоторый тестовый код, который, как я думал, будет работать:

// Create a new event handler that takes in the function I want to execute when the event fires
EventHandler myEventHandler = new EventHandler(myObject_SomeEvent);
// Get "p1" number events that got hooked up to myEventHandler
int p1 = myEventHandler.GetInvocationList().Length;
// Now actually hook an event up
myObject.SomeEvent += m_myEventHandler;
// Re check "p2" number of events hooked up to myEventHandler
int p2 = myEventHandler.GetInvocationList().Length;

К сожалению, вышесказанное совершенно неверно. Я думал, что каким-то образом "invocationList" в myEventHandler будет автоматически обновляться, когда я подключаю к нему событие. Но нет, это не так. Длина этого всегда возвращается как один.

Можно ли как-то определить это извне объекта, содержащего событие?


person Nick    schedule 15.07.2009    source источник


Ответы (5)


Ключевое слово C# event создает тонкую иллюзию, заключающуюся в том, что у события есть список вызовов.

Если вы объявите событие с помощью ключевого слова C# event, компилятор создаст частный делегат в вашем классе и будет управлять им за вас. Всякий раз, когда вы подписываетесь на событие, вызывается сгенерированный компилятором метод add, который добавляет обработчик события в список вызовов делегата. Для события нет явного списка вызовов.

Таким образом, единственный способ получить доступ к списку вызовов делегата — предпочтительно:

  • Используйте отражение для доступа к делегату, сгенерированному компилятором, ИЛИ
  • Создайте незакрытый делегат (возможно, внутренний) и реализуйте методы добавления/удаления события вручную (это не позволяет компилятору генерировать реализацию события по умолчанию)

Вот пример, демонстрирующий последний метод.

class MyType
{
    internal EventHandler<int> _delegate;
    public event EventHandler<int> MyEvent;
    {
        add { _delegate += value; }
        remove { _delegate -= value; }
    }
}
person Steve Guidi    schedule 15.07.2009
comment
Одно пояснение, в котором я не был уверен до того, как попробовал, заключается в том, что сгенерированный компилятором делегат имеет то же имя, что и событие, которое вы объявили в своем коде (или, по крайней мере, в моем). - person Matt Zappitello; 18.01.2013

Если соответствующий объект указал ключевое слово события, то вы можете только добавить (+=) и удалить (-=) обработчики, и ничего более.

Я считаю, что сравнение длины списка вызовов будет работать, но вам нужно работать внутри объекта, чтобы добраться до него.

Также имейте в виду, что операторы += и -= возвращают новый объект события; они не изменяют существующий.

Почему вы хотите знать, связано ли конкретное событие? Чтобы не регистрироваться несколько раз?

Если это так, хитрость заключается в том, чтобы сначала удалить обработчик (-=), поскольку удаление обработчика, которого нет, является законным и ничего не делает. Например:

// Ensure we don't end up being triggered multiple times by the event
myObject.KeyEvent -= KeyEventHandler;
myObject.KeyEvent += KeyEventHandler;
person Bevan    schedule 15.07.2009
comment
если вы сделаете += KeyEventHandler более одного раза, будет ли -= KeyEventHandler удалять все или только последний, только первый? - person vbp13; 12.04.2018
comment
-= удалит один; Я не знаю, как определить, какой из них, учитывая, что все они равны. - person Bevan; 13.04.2018

Это можно сделать, но потребуется некоторая хитрость... как упоминалось выше, компилятор генерирует реализацию события, включая его резервное поле. Отражение позволяет вам получить резервное поле по имени, и, получив к нему доступ, вы можете вызвать GetInvocationList(), даже если вы находитесь за пределами самого класса.

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

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string typeName = "ConsoleApplication1.SomeClass, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
            string eventName = "SomeEvent";

            Type declaringType = Type.GetType(typeName);
            object target = Activator.CreateInstance(declaringType);

            EventHandler eventDelegate;
            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null) { Console.WriteLine("No listeners"); }

            // attach a listener
            SomeClass bleh = (SomeClass)target;
            bleh.SomeEvent += delegate { };
            //

            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null)
            { 
                Console.WriteLine("No listeners"); 
            }
            else
            { 
                Console.WriteLine("Listeners: " + eventDelegate.GetInvocationList().Length); 
            }

            Console.ReadKey();

        }

        static EventHandler GetEventHandler(object classInstance, string eventName)
        {
            Type classType = classInstance.GetType();
            FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField
                                                               | BindingFlags.NonPublic
                                                               | BindingFlags.Instance);

            EventHandler eventDelegate = (EventHandler)eventField.GetValue(classInstance);

            // eventDelegate will be null if no listeners are attached to the event
            if (eventDelegate == null)
            {
                return null;
            }

            return eventDelegate;
        }
    }

    class SomeClass
    {
        public event EventHandler SomeEvent;
    }
}
person STW    schedule 28.10.2009
comment
Я нашел этот ответ весьма полезным, хотя метод GetEventHandler() вернулся с нулевым полем событий. (Я подозреваю, что это связано с тем, что я передал в подпрограмму динамический прокси-сервер на основе Castle вместо проксируемого объекта.) Я находился в процессе демонстрации того, как динамические прокси-серверы могут использоваться для автоматической реализации INotifyPropertyChanged. - person Wonderbird; 03.04.2014
comment
Вы можете просто вернуть eventDelegate из метода GetEventHandler, не выполняя последнюю нулевую проверку. - person Chris Lees; 22.04.2015
comment
Я получаю сообщение об ошибке: «Невозможно преобразовать объект типа «CallStatechanged» в тип «System.EventHandler». CallStateChanged — это имя моего события. - person Avdhut Vaidya; 28.08.2016
comment
GetField возвращает ноль. GetEvent возвращает нужное событие, но не позволяет вызвать GetValue, поскольку EventInfo не содержит этого метода. - person Otto Abnormalverbraucher; 22.12.2016
comment
Приведение к EventHandler не является достаточно общим, я предлагаю использовать return (Delegate)eventField.GetValue(classInstance) - person Ziriax; 18.05.2017

Вы должны иметь возможность получить список вызовов через «событие». Грубо говоря, это будет что-то вроде..

public delegate void MyHandler;
public event MyHandler _MyEvent
public int GetInvocationListLength()
{
   var d = this._MyEvent.GetInvocationList(); //Delegate[]
   return d.Length;
}
person user20155    schedule 15.07.2009
comment
Это будет работать только внутри класса, где объявлено событие; он пытается сделать это снаружи. - person Pavel Minaev; 15.07.2009

Я использовал ваш пример и немного изменил его. регистрация обработчика событий увеличивает количество вызовов. даже при использовании двух разных методов обратного вызова (как показано здесь) или при использовании одного и того же метода обратного вызова.

private void SomeMethod()
{
    // Create a new event handler that takes in the function I want to execute when the event fires
    var myEventHandler = new EventHandler(OnPropertyChanged);

    // Get "p1" number events that got hooked up to myEventHandler
    int p1 = myEventHandler.GetInvocationList().Length; // 1

    // Now actually hook an event up
    myEventHandler += OnPropertyChanged2;

    // Re check "p2" number of events hooked up to myEventHandler
    int p2 = myEventHandler.GetInvocationList().Length; // 2

    myEventHandler.Invoke(null, null); 
// each of the registered callback methods are executed once. 
// or if the same callback is used, then twice.
}

private void OnPropertyChanged2(object? sender, EventArgs e)
{}
private void OnPropertyChanged(object? sender, EventArgs e)
{}

Как уже упоминалось, доступ к eventhandler.GetInvocationList ограничен самим классом, вам необходимо предоставить свойство или метод для получения списка делегатов.

Нравится:

protected Delegate[]? GetInvocations() => PropertyChanged?.GetInvocationList();

в зависимости от вашего использования сделайте его защищенным, внутренним или обоими.

person Sven Krauter    schedule 15.12.2020