Перехватить переменную в 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);
Меня это беспокоит в два раза.
- Есть ли проблема с областью, где обработчик фактически исчезнет без удаления, как только я выйду за пределы функции, к которой он прикреплен? Я видел некоторые странные ошибки в прошлом, когда обработчик событий исчезал, когда я использовал лямбда-выражение. Не всегда, просто в некоторых случаях. Может ли кто-нибудь объяснить мне, в каких случаях это может быть, чтобы я знал, когда безопасно использовать синтаксис, который у меня был.
- Я не могу вспомнить точно, но я не думаю, что когда-нибудь смогу удалить обработчик событий, если бы использовал этот синтаксис, потому что созданный неявный объект не тот.
Из-за этих двух проблем я думал создать 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. Что-то не так с этим подходом?