views:

74

answers:

1

Consider

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

The reason printAction will be called by _captureAction is that the line

action -= printAction;

Actually translates into

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

so the action captured by _captureAction in CaptureActionFromParam() is not changed - only the local 'action' variable in TestSimpleCapturedAction() is affected.

My desired behavior in such a scenario would be printAction not being called. The only solution I can think of is defning a new "delegate container" class as such:

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

This works but I wonder if my desired behavior can be achieved without introducing this new layer of abstraction. Implementing the strategy pattern can easily lead to such a situation, so one would reckon the language and/or the BCL would support it natively somehow.

Thanks !

+6  A: 

Delegates are like strings. They're implemented as reference types, but they behave more like immutable value types. When you add or subtract characters on a string, it doesn't change the string, it produces a new string that is the new result. When you add or subtract numbers from an integer, it doesn't change the integer, it produces a new integer that is the new result. And when you add or substract a delegate from a delegate, it doesn't change either delegate; it produces a new delegate which is the result.

If what you want to capture is a delegate which can vary then capture a variable that contains a reference to a delegate. Variables vary, that's why they're called "variables". If you want something that can vary, get the variable.

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

Now the delegate that is captured has itself captured the variable "action", not the value that happens to be in it.

Remember:

  • Parameters are passed by value.
  • Lambdas capture variables, not values.

Make sense?

Eric Lippert
Wow, An answer from the man himself! I am actually aware of your points, which is why I proposed the "Mutable Action" class solution above (MutalbeAction would have been a better name than ActionContainer), but CaptureActionFromParam(() => action()) is far more elegant. Thanks !
ohadsc