Захват делегатов в анонимных методах - PullRequest
4 голосов
/ 20 февраля 2010

Рассмотрим

    Action _captureAction;
    private void TestSimpleCapturedAction()
    {
        Action action = new Action(delegate { });
        Action printAction = () => Console.WriteLine("Printing...");

        action += printAction;
        CaptureActionFromParam(action);
        action -= printAction;

        _captureAction(); //printAction will be called!
    }

    private void CaptureActionFromParam(Action action)
    {
        _captureAction = () => action();
    }

Причиной вызова функции printAction _captureAction является то, что строка

action -= printAction;

На самом деле переводится как

action = (Action) Delegate.Remove(action, printAction);

поэтому действие, захваченное _captureAction в CaptureActionFromParam (), не изменяется - затрагивается только локальная переменная 'action' в TestSimpleCapturedAction ().

Мое желаемое поведение в таком сценарии было бы, чтобы printAction не вызывался. Единственное решение, которое я могу придумать, это определить новый класс «контейнер делегатов» следующим образом:

    class ActionContainer
    {
        public Action Action = new Action(delegate { });
    }

    private void TestCapturedActionContainer()
    {
        var actionContainer = new ActionContainer();
        Action printAction = () => Console.WriteLine("Printing...");

        actionContainer.Action += printAction;
        CaptureInvoker(actionContainer);
        actionContainer.Action -= printAction;

        _captureAction();
    }

    private void CaptureInvoker(ActionContainer actionContainer)
    {
        _captureAction = () => actionContainer.Action();
    }

Это работает, но мне интересно, можно ли достичь желаемого поведения без введения этого нового уровня абстракции. Реализация паттерна стратегии может легко привести к такой ситуации, поэтому можно было бы считать, что язык и / или BCL каким-то образом поддержат его.

Спасибо!

1 Ответ

7 голосов
/ 20 февраля 2010

Делегаты похожи на строки. Они реализованы как ссылочные типы, но ведут себя больше как неизменяемые типы значений. Когда вы добавляете или вычитаете символы в строке, она не меняет строку, она создает новую строку, которая является новым результатом. Когда вы добавляете или вычитаете числа из целого числа, оно не меняет целое число, оно создает новое целое число, которое является новым результатом. И когда вы добавляете или вычитаете делегата из делегата, это не меняет ни одного делегата; он создает новый делегат, который является результатом.

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

    CaptureActionFromParam(()=>{action();}); 

Теперь захваченный делегат сам захватил переменную "действие", а не значение , которое в нем находится.

Помните:

  • Параметры передаются значением .
  • Захват лямбды переменных , а не значений .

Имеет смысл?

...