2009-09-07 3 views
17

У меня есть следующий код:C# - анонимные функции и обработчики событий

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
{ 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e) 
         { 
         if (e.Step.ResourceType == res) retval.Add(e.Step); 
         }; 
    this.Start(); 
    return retval; 
} 

Обратите внимание, как я зарегистрировать мой член события (FoundStep) местную на месте анонимной функции.

Мой вопрос: когда функция «FindStepByType» закончится - анонимная функция будет удалена автоматически из списка делегатов события или мне придется вручную удалить ее, прежде чем выходить из функции? (и как это сделать?)

Надеюсь, мой вопрос был ясен.

ответ

34

Ваш код имеет несколько проблем (некоторые вы и другие определили):

  • анонимный делегат не может быть удален из события, как кодируется.
  • Анонимный делегат будет жить дольше, чем жизнь метода, вызвавшего его, потому что вы добавили его в FoundStep, который является членом этого.
  • Каждая запись в FindStepsByType добавляет еще одного анонимного делегата в FoundStep.
  • анонимный делегат является закрытие и эффективно увеличивает срок службы RetVal, так что даже если вы перестанете ссылки RetVal в другом месте в коде, он по-прежнему проводится анонимный делегат.

Чтобы это исправить, и по-прежнему использовать анонимный делегат, назначить его в локальную переменную, а затем удалить обработчик внутри наконец блока в (необходимо в случае, если обработчик сгенерирует исключение):

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    EventHandler<WalkerStepEventArgs> handler = (sender, e) => 
    { 
     if (e.Step.ResourceType == res) retval.Add(e.Step); 
    }; 

    this.FoundStep += handler; 

    try 
    { 
     this.Start(); 
    } 
    finally 
    { 
     this.FoundStep -= handler; 
    } 

    return retval; 
    } 

с # 7.0+ вы можете заменить анонимный делегат с локальной функции, достижения того же эффекта:

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
     var retval = new List<IWFResourceInstance>(); 

     void Handler(object sender, WalkerStepEventArgs e) 
     { 
      if (e.Step.ResourceType == res) retval.Add(e.Step); 
     } 

     FoundStep += Handler; 

     try 
     { 
      this.Start(); 
     } 
     finally 
     { 
      FoundStep -= Handler; 
     } 

     return retval; 
    } 
5

Нет, он не будет удален автоматически. В этом смысле нет никакой разницы между анонимным методом и «обычным» методом. Если вы хотите, вы должны вручную отказаться от подписки на мероприятие.

Собственно, он будет захватывать другие переменные (например, res в вашем примере) и сохранять их в живых (также запрещает сборщик мусора).

+0

Не так ли, как с использованием предикатов? Когда я использую предикаты, я не освобождаю делегата предиката. –

+2

Предикаты не сохраняются нигде, но здесь вы подписываетесь на событие. Пока объект, содержащий событие, жив, он будет ссылаться на ваш делегат и косвенно на его переменные. Когда вы передаете, скажите: «.Where (x => x.Hidden)» к некоторому методу, метод будет работать с ним и выкидывать его (это всего лишь локальная переменная, что касается метода 'Where'. Это не относится к вашему делу. Кроме того, если 'Where' где-то его где-то хранит, вы тоже должны были бы беспокоиться об этом. –

3

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

Если вы посмотрите на свой код, даже если вы заявляете и подписываетесь на мероприятие в функции, событие, на которое вы подписаны, находится в классе, поэтому после его подписания он будет всегда подписан даже после выхода функции. Другая важная вещь, которую нужно понять, - это то, что каждый раз, когда вызывается эта функция, она снова будет подписана на событие. Это совершенно законно, так как события, по сути, являются многоадресными делегатами и позволяют нескольким подписчикам. (Это может быть или не быть тем, что вы намерены.)

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

По этим причинам, если вам придется отказаться от подписки на мероприятие в какой-то более поздний момент, не рекомендуется использовать анонимных делегатов. См. How to: Subscribe to and Unsubscribe from Events (C# Programming Guide) (в частности, раздел «Подписка на события с использованием анонимного метода»).

5

Ниже подход о том, как отказ от подписки события в Оймурный метод:

DispatcherTimer _timer = new DispatcherTimer(); 
_timer.Interval = TimeSpan.FromMilliseconds(1000); 
EventHandler handler = null; 

int i = 0; 

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev) 
{ 
    i++; 
    if(i==10) 
     _timer.Tick -= handler; 
}); 

_timer.Start(); 

 Смежные вопросы

  • Нет связанных вопросов^_^