Подтвердить что ты не робот

Передавать событие как параметр методу

Возможный дубликат:
Как передать событие методу?

Можно ли передать событие в качестве параметра методу?

Например, следующий метод подписывается на событие, выполняет работу и отменяет подписку на событие:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
        IEnumerable<TElement> elements,
        ??? elementEvent)
    where TEventArgs: EventArgs
{
    EventHandler<TEventArgs> handler = (sender, e) => { /* Handle an event */ };

    foreach (var element in elements)
    {
         // Subscribe somehow
         element.elementEvent += handler
    }

    // Do things

    foreach (var element in elements)
    {
         // Unsubscribe somehow
         element.elementEvent -= handler
    }
}

Клиентский код:

var elements = new [] { new Button(), new Button() };
SubscribeDoAndUnsubscribe(elements, ??? /* e => e.Click */);

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

4b9b3361

Ответ 1

Фактически вы обнаружили, что события не являются "первоклассниками" на С#; вы не можете передавать событие в виде данных. Вы можете передать делегат методу, связанному с получателем, как первоклассному объекту, сделав делегат. Вы можете передать ссылку на любую переменную как объект (в основном) первого класса. (Я говорю "в основном", потому что ссылки на переменные не могут быть сохранены в полях, хранятся в массивах и т.д., Они сильно ограничены по сравнению с другими видами данных.) Вы можете передавать тип, получая его объект Type и передавая его вокруг.

Но нет возможности напрямую передавать данные как событие, свойство, индексатор, конструктор или деструктор, связанный с конкретным экземпляром. Лучшее, что вы можете сделать, это сделать делегата (или пару делегатов) из лямбда, как вы предлагаете. Или получить объект отражения, связанный с событием, и передать его вместе с экземпляром.

Ответ 2

Нет, к сожалению нет.

Если вы посмотрите на Reactive Extensions, который испытывает аналогичную проблему. Три варианта, которые они используют (IIRC - прошло некоторое время с тех пор, как я посмотрел):

  • Перейдите в соответствующий EventInfo и вызовите его с отражением
  • Передайте имя события (и, если необходимо, цель) и вызовите его с отражением
  • Передача делегатов для подписки и отписки

В последнем случае вызов будет выглядеть следующим образом:

SubscribeAndDoUnsubscribe(elements,
                          handler => e.Click += handler,
                          handler => e.Click -= handler);

и объявление будет выглядеть следующим образом:

void SubscribeDoAndUnsubscribe<TElement, TEventArgs>(
        IEnumerable<TElement> elements,
        Action<EventHandler<TEventArgs>> subscription,
        Action<EventHandler<TEventArgs>> unsubscription)
    where TEventArgs: EventArgs

Ответ 3

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

void DoSomethingOnSomethingElse(T obj, Action method)
{
    obj.method();
}

С# не работает. Как компилятор знает, что все T имеют метод method? Это не так и не может. Аналогично, не каждый TElement в вашем коде будет иметь событие Click, например.

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

EventHandler handler = null;
handler = (s,e) => 
{
    DoSomething(e);
    var b = (Button) s;
    b.Click -= handler;
}

foreach (var button in buttons) 
{
    button.Click += handler;
}

Это, очевидно, работает только с кнопками, но, как я пишу это, я вижу, что Джон Скит показал вам более общее решение, поэтому я закончу здесь.