0

Just coding for a bit of fun and to get my head round anonymous methods, etc. I have a class whose main purpose is to run a lambda in a loop. The lambda has an out parameter (exit) which gets passed back into the class. Now I can nest these as per the code below where l2 is declared in the lambda for l1.

            int outer = 0;

            Loop l1 = new Loop().Infinite((out bool outerExit) =>
            {
                int inner = 0;
                outer++;

                Loop l2 = new Loop().Infinite((out bool innerExit) =>
                {
                    inner++;
                    Debug.WriteLine($"{outer}-{inner}");
                    innerExit = inner >= 3;
                    outerExit = inner >= 3;
                });

                //outerExit = outer >= 3;
            });

            Assert.Equal(3, outer);

However this gives an error in the l2 lambda where i assign a value to outerExit.

Error CS1628 Cannot use ref, out, or in parameter 'outerExit' inside an anonymous method, lambda expression, query expression, or local function

The idea is to exit both Loops from within the inner loop when a certain criteria is met.

For those interested.

public class Loop
    {
        //this is a delegate TYPE!!! that matches our function
        public delegate void ExitableAction<T1>(out T1 a);

        //in thiscase it is a bool
        protected ExitableAction<bool> action;

        protected LoopType type;

        public Loop Infinite(ExitableAction<bool> actn)
        {
            action = actn;
            type = LoopType.Infinite;

            Start();

            return this;
        }

        protected void Start()
        {
            bool exit = false;

            while(!exit)
            {
                action.Invoke(out exit);
            }
        }
    }
ThirdPrize
  • 194
  • 1
  • 13
  • @Sweeper added. – ThirdPrize May 05 '21 at 10:25
  • The error is very specific. You a lambda cannot have an `out` parameter – Aluan Haddad May 05 '21 at 10:31
  • There's no need for them either because you have access to both of the variables corresponding to the these parameters because they are in scope. Closures for the win – Aluan Haddad May 05 '21 at 10:33
  • 1
    Does this answer your question? [Cannot use ref or out parameter in lambda expressions](https://stackoverflow.com/questions/1365689/cannot-use-ref-or-out-parameter-in-lambda-expressions) – Aluan Haddad May 05 '21 at 10:34
  • So do you want it to output 1-1, 1-2, 1-3 and then stop? `outer`'s final value would be 1 then, not 3? – Sweeper May 05 '21 at 10:35
  • Remove the parameters themselves. Just a sign to the variables inside of the lambdas. They are in scope. – Aluan Haddad May 05 '21 at 10:38
  • @AluanHaddad the Loop class runs the lambda in a loop unless you tell it to stop. that is what the parameter is for. A bit like a cancellation token. If i remove the parameters, how would i pass the value back into the Loop? – ThirdPrize May 05 '21 at 11:04
  • the actual output is immaterial. i just want to "leave" the outer loop from within the inner loop. – ThirdPrize May 05 '21 at 11:12
  • Can you define what "leave" means? Do you want to continue to run the remaining portion of the loop body (of either the inner loop or outer loop), and then stop, or stop immediately at that point? Depending on what kind of "leave" you want, the output will be affected, so clearly it is not immaterial... – Sweeper May 05 '21 at 11:36
  • @Sweeper ok. i want it to finish that iteration of that loop/lambda and not run again. Not immediately drop out. – ThirdPrize May 05 '21 at 11:45
  • @ThirdPrize as I thought :) My answer should hopefully work. – Sweeper May 05 '21 at 11:55

1 Answers1

1

For why you can't use ref/out parameters, see this post.

You seem to want to recreate a "loop" statement with a "break" functionality. Rather than using out parameters to implement the break statement, you can use another Action<bool> delegate instead.

public class Loop
{

    protected Action<Action<bool>> action;

    public Loop Infinite(Action<Action<bool>> actn)
    {
        action = actn;

        Start();

        return this;
    }

    protected void Start()
    {
        bool exit = false;

        while (!exit)
        {
            // passes the Action<bool> that assigns the given bool to exit
            action((b) => exit = b); 
        }
    }
}
int outer = 0;

Loop l1 = new Loop().Infinite(setOuterExit =>
{
    int inner = 0;
    outer++;

    Loop l2 = new Loop().Infinite(setInnerExit =>
    {
        inner++;
        Console.WriteLine($"{outer}-{inner}");
        setInnerExit(inner >= 3);
        setOuterExit(inner >= 3);
    });

    setOuterExit(outer >= 3);
});

This gives a final outer value of 3. But if you wanted that, you could have just removed the setOuterExit call in the inner loop - it also gives 3 as output.

Right now the value assigned to exit by the inner setOuterExit call is being "overwritten" by the outer setOuterExit call, so the outer loop doesn't break immediately when it hits the inner setOuterExit. We could change the assignment to exit to:

action((b) => exit |= b); 

So that if exit is ever set to true, no other values can overwrite it. This will solve this problem, but note that setOuterExit still won't work the same as a real break; statement. The code immediately after setOuterExit still gets executed, for example.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • @ThirdPrize yes, and instead we use the lambda capturing mechanism to set the `exit` variable. – Sweeper May 05 '21 at 12:43
  • See if i have got this right. The parameter is no longer an "out" so the outer parameter can be referenced by the inner lambda. Action> is an action that returns an action that returns bool. outerExit(outer >= 3) sets the sub action to a bool value and passes it back. action((b) => exit = b) calls the outer action and sets exit to the bool value that was passed back. cunning. – ThirdPrize May 05 '21 at 12:49
  • @ThirdPrize I think you mostly understood correctly. The big misunderstanding is that `Action>` represents a function that takes a parameter of type `Action`, which in turn represents a function that takes a parameter of type `bool`. `action((b) => exit = b)` calls the lambda you passed to `Infinite`, and setting `setInnerExit`/`setOuterExit` (which is a function that takes a bool parameter) to be the lambda that sets `exit` to that `bool` parameter. This way, when you call `setOuterExit(outer >= 3)`, you are actually doing `exit = outer >= 3`. – Sweeper May 05 '21 at 13:01