С# - анонимные функции и обработчики событий

У меня есть следующий код:

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

По этим причинам, если вам придется отказаться от подписки на мероприятие в какой-то более поздний момент, не рекомендуется использовать анонимных делегатов. См. Как подписаться и отменить подписку на события (Руководство по программированию на С#) (в частности, раздел "Подписка" к событиям с использованием анонимного метода ").