Перехватить переменную в EventHandler

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

У меня есть обработчик событий, который присоединен к объекту, который является свойством другого класса. В моем обработчике событий мне нужны дополнительные метаданные об объекте, который вызвал событие (т. Е. Идентификатор объекта, в котором он содержится). От отправителя и информации о событии нет способа получить нужную мне информацию. Я склонялся к тому, что это было бы хорошим местом для использования захваченной переменной, но я не уверен в своих идеях реализации.

Поэтому для иллюстрации в коде у меня есть обработчик событий:

void MyEventHandler(object sender, EventArgs e){
    //Do Stuff here
}

(Как примечание, я использую здесь базовые EventArgs, но в моей фактической реализации это специализированный подкласс, и событие объявляется с использованием универсального EventHandler)

Я сейчас прикрепляю это так:

topObject.SubObject.EventToHandle += MyEventHandler;

Позже я отключу его так:

topObject.SubObject.EventToHandle -= MyEventHandler;

Я хочу, чтобы идентификатор topObject обрабатывал событие, поэтому я собирался изменить MyEventHandler, чтобы иметь следующую подпись:

void MyEventHandler(int id, object sender, EventArgs e)

и прикрепите обработчик события следующим образом:

topObject.SubObject.EventToHandle += (s,e) => MyEventHandler(topObject.ID, s,e);

Меня это беспокоит в два раза.

  1. Есть ли проблема с областью, где обработчик фактически исчезнет без удаления, как только я выйду за пределы функции, к которой он прикреплен? Я видел некоторые странные ошибки в прошлом, когда обработчик событий исчезал, когда я использовал лямбда-выражение. Не всегда, просто в некоторых случаях. Может ли кто-нибудь объяснить мне, в каких случаях это может быть, чтобы я знал, когда безопасно использовать синтаксис, который у меня был.
  2. Я не могу вспомнить точно, но я не думаю, что когда-нибудь смогу удалить обработчик событий, если бы использовал этот синтаксис, потому что созданный неявный объект не тот.

Из-за этих двух проблем я думал создать Action, сохранить действие и использовать его, пока мне не понадобится удалить обработчик событий. Я сделал следующее:

Action<object, EventArgs> handler = (s,e) => MyEventHandler(topObject.ID, s,e);
topObject.SubObject.EventToHandle += handler;

Я понял, что действие не может быть преобразовано в обработчик события. Есть ли какой-нибудь простой способ сделать это преобразование, которое по-прежнему будет гарантировать, что я могу отсоединить обработчик событий? Неужели я просто подумал над этим / есть ли способ, которым я не вижу сейчас, чтобы сделать это?

4 ответа

Решение

Вы можете создать все удобные функции-оболочки, которые обертывают существующие обработчики событий и предоставляют им идентификаторы объектов, но вам все равно придется явно сохранять полученный делегат, чтобы отписаться от события.

Единственный хороший способ сделать это без уродливых обёрток - это использовать реактивные расширения. По сути, он позволяет вам преобразовать ваше событие в IObservable, а затем вы можете применить любой оператор к результирующему IObservable (например, Select выполнит работу в вашем случае). Но все равно это не так элегантно.

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

Другими словами, ваш обработчик событий может выглядеть так:

void Handler(object sender, EventArgs e)
{
     var s = (SubObject) sender;
     int id = s.TopObject.ID;

     // do something with id...
}

Я бы сохранил подписи событий в соглашении отправителя и аргументов.

Не меняйте подпись события. Хотя технически CLR допускает любую подпись для события, существуют причины, по которым вся платформа была разработана с событиями, имеющими подпись. (object sender, EventArgs args), Фактически, существует правило FxCop для событий, которые нарушают эту подпись, CA1009: правильно объявляйте обработчики событий:

Методы обработчика событий принимают два параметра. Первый имеет тип System.Object и называется "отправитель". Это объект, вызвавший событие. Второй параметр имеет тип System.EventArgs и называется "e".

Есть несколько решений (альтернатив):

  • передайте topObject.ID как член ваших пользовательских EventArgs.
  • создайте объект-оболочку, который инкапсулирует topObject.ID, и подключите обработчик события к методу этого объекта.
  • используйте область закрытия, которая может сохранить ссылку на topObject.ID в области (которая идентична методу, описанному выше, но тяжелый перенос выполняется компилятором)

Подпись обработчика события в классе, вызывающем событие, должна быть:

protected void OnMyEvent(object sender, EventArgs  e)
{
    ....
}

или же

protected void OnMyEvent(object sender, MyEventArgs  e)
{
    ....
}

В этом случае вызывающая сторона будет делать такой код:

topObject.SubObject.MyEvent -= OnSubObjectMyEvent;

и реализовать OnSubObjectMyEvent следующим образом (пример):

private void OnSubObjectMyEvent(object sender, MyEventArgs e)
{
   int topObjectId = ((SubObjectType)sender).TopObject.Id;
   ...
}

Здесь я предполагаю, что SubObject имеет свойство TopObject, которое позволяет мне получить идентификатор верхнего объекта.

Именно так работает большинство классов.NET Framework. Что-то не так с этим подходом?

Другие вопросы по тегам