В 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" закончится-будет ли анонимная функция автоматически удалена из списка делегатов события или мне придется вручную удалить ее перед тем, как остановить функцию? (и как мне это сделать?)

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

4 ответов


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

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

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

  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;
  }

С помощью C# 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;
    }

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

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


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

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();

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

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

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

по этим причинам, Если вам придется отказаться от подписки на событие, не рекомендуется использовать анонимные делегаты. См.Как: подписаться и отказаться от подписки на события (руководство по программированию на C#) (в частности, раздел под названием "подписаться на события с помощью анонимного метода").