Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Labeled loops like in Java #1597

Closed
AustinBryan opened this issue Jun 2, 2018 · 114 comments
Closed

[Proposal] Labeled loops like in Java #1597

AustinBryan opened this issue Jun 2, 2018 · 114 comments

Comments

@AustinBryan
Copy link

AustinBryan commented Jun 2, 2018

Currently the cleanest way to break out of a nested loop is this:

for (int i = 0; i < 5; i++)
    for (int j = 0; j < 3; j++)
        if (i * j == 10) 
            goto outer;

outer: {}

If all we wanted to do was break out of the loops, we have to do outer: {}, which is sort've clunky. What we could do instead is use labeled loops like Java, so we can do this:

outer: for (int i = 0; i < 5; i++)
    inner: for (int j = 0; j < 3; j++)
        if (i * j == 10) 
            break outer;

And instead of:

int j = 0;

for (int i = 0; i < 5; i++)
    inner: for (; j < 3; j++)
        if (i * j == 10) 
            goto inner;

We can just do:

for (int i = 0; i < 5; i++)
    inner: for (j = 0; j < 3; j++)
        if (i * j == 10) 
            continue inner;

The alternative is cleaner, more intuitive, and simpler.

I saw another post where they suggest doing break 1; to break the first loop, but that doesn't refactor well if the number of loops changes, you have to carefully make sure the numbers are correct.

@iam3yal
Copy link
Contributor

iam3yal commented Jun 2, 2018

@AustinBryan Related #869

@Joe4evr
Copy link
Contributor

Joe4evr commented Jun 2, 2018

Also relevant: dotnet/roslyn#5883

@jnm2
Copy link
Contributor

jnm2 commented Jun 2, 2018

I like labeled break much better. Also would continue outer; work?

@AustinBryan
Copy link
Author

@jnm2

Also would continue outer; work?

Yes, it should. I just tested it out on Java and it works for that so it should for C#.
Labeled breaks are the cleanest, most straight foreward, and least error prone way to do manage nested loops.

@AustinBryan AustinBryan changed the title Labeled loops like in Java [Proposal] Labeled loops like in Java Jun 2, 2018
@quinmars
Copy link

quinmars commented Jun 3, 2018

In the last three years I needed only once a goto statement to leave a nested foreach loop. Later on I replaced it by a single foreach loop thanks to LINQ. I don't realy understand the absolute avoidance of goto. There are rare cases (and they are very rare) where it comes in handy to use it and there is nothing wrong about it.

@iam3yal
Copy link
Contributor

iam3yal commented Jun 3, 2018

@AustinBryan I think that your second case was rejected, you can check the post @Joe4evr linked.

@Neme12
Copy link

Neme12 commented Jun 3, 2018

So break outer would essentially be the same as goto except that it would jump after the labeled statement? I don't see much value in that.

@jnm2
Copy link
Contributor

jnm2 commented Jun 3, 2018

@Neme12 It's clearer about what's going on. More symmetrical with continue outer and guaranteed to be an acceptable kind of goto.

@AustinBryan
Copy link
Author

@Neme12 I mean yes, except it reflects more on what we want to do. In this example, what I want to do is break outer, not goto some random empty statement. continue outer is much simpler than the alternative that uses goto

@DavidArno
Copy link

Currently the cleanest way to break out of a nested loop is this:

I disagree. the cleanest way is to exit the function, avoiding goto and break:

void LoopForTheFunOfIt()
{
    for (int i = 0; i < 5; i++)
        for (int j = 0; j < 3; j++)
            if (i * j == 10)
                return; 
}

If you end up with code that needs a goto, don't reach for labelled breaks, reach for the refactoring tools and split out a new method.

@theunrepentantgeek
Copy link

If you end up with code that needs a goto, don't reach for labelled breaks, reach for the refactoring tools and split out a new method.

I kind of agree - in code reviews, I usually see nested loops as a code smell indicating a need to break out another method.

@iam3yal
Copy link
Contributor

iam3yal commented Jun 5, 2018

@theunrepentantg

I usually see nested loops as a code smell indicating a need to break out another method.

Wouldn't that be an extreme? I mean, indeed, sometimes it's the case but saying that you usually tend to find nested loops a code smell when a code smell should indicate that there's a problem with the design not with how the code is structured but more importantly this statement shouldn't be taken as rule of thumb because what's applicable to one codebase might not be applicable to another, one might be more imperative in nature whereas the other can be more declarative and so it really depends.

Refactoring too much can also lead to readability and maintainability issues.

@theunrepentantgeek
Copy link

Refactoring too much can also lead to readability and maintainability issues.

I totally agree - readability, maintainability - and all the 'ilities (scalability, supportability, etc) are very important.

Nonetheless, I stand by the statement: Usually nested loops are a design smell.

a code smell should indicate that there's a problem with the design not with how the code is structured

I don't understand this - how can well-designed code be structured poorly? Design covers all scales - from the macro architecture (components, services, etc) to the micro (algorithm choice, method signatures, local variable naming, etc).

@iam3yal
Copy link
Contributor

iam3yal commented Jun 5, 2018

don't understand this - how can well-designed code be structured poorly?

@theunrepentantgeek I guess that this is where we disagree, you seems to think that nested loops is an indication of a code smell whereas I think that it might be the case but you don't always need to refactor nested loops especially in this case where the language can be improved a bit and make the goto statement more convenient to work with so just because some people decide to ban it or don't see any use for it doesn't mean people who do shouldn't use it and instead introduce methods into their codebases that wouldn't really improve anything.

You can structure the code in many different ways, you can structure it through a series of imperative statements, you can use a more procedural approach, you can use a more declarative approach and apply all kinds of functional programming techniques and yet the design can be broken so just because you refactored something into its own method and you gave it a name and it looks good doesn't mean you improved the design, in fact, it might mean the opposite but really context is important here and in this case the kind of codebase you work with should be considered before going out and tell people what they should or shouldn't do.

Now, some people would consider their codebase state of the art because it's refactored and follow awesome set of principles and whatnot only to find out that it's actually poorly designed.

@DavidArno
Copy link

...you seems to think that nested loops is an indication of a code smell...

Such code runs counter to the "code should describe the intent; not the mechanism" principle of easy-to-reason (or "reasonable") code. So if I write code like:

var found = false; 
for (var i = 0; i < range1; i++)
for (var j = 0; j < range2; j++)
    if (i * j == 10)
    {
       found = true;
       goto outer;
    }
outer: {}

I'm purely expressing the mechanism. The code is highly unreasonable. Therefore the next thing I'm likely to do is add a comment to explain its intent:

// loop through the two ranges and report if they contain factors of 10
var found = false; 
for (var i = 0; i < 5; i++)
for (var j = 0; j < 3; j++)
    if (i * j == 10)
    {
       found = true;
       goto outer;
    }
outer: {}

Now I have the intent, but I still have the mechanism there too. It's a mess. I'm not sure If it's a code smell, but it certainly is ugly and difficult to read. What I want is the intent only:

var found = RangesContainFactorsOfTen(5, 3);

The code is now trivially easy to reason. And when I want to understand the mechanism, I head off to that method:

private bool RangesContainFactorsOfTen(int range1, int range2)
{
    for (var i = 0; i < range1; i++)
    for (var j = 0; j < range2; j++)
        if (i * j == 10)
            return true;

    return false;
}

So I've achieved code that is far easy to reason and read, I've removed the comment and I've avoided the need for a goto or labelled break (which is just syntactic sugar for a goto).

C# is already complicated enough with a growing number of features with every release. Those new features should be focused on the "pit of success", ie making it easier to write good code. Labelled breaks are the opposite: they just offer another way, in addition to the existing one, of writing bad code. So they they have no place in future versions of the language in my view.

@iam3yal
Copy link
Contributor

iam3yal commented Jun 5, 2018

@DavidArno First of all, I agree, if there's a value in refactoring, you should do it but my point was that if you have a method that is self-explanatory you wouldn't want to refactor it further just because now you need a goto statement.

Those new features should be focused on the "pit of success", ie making it easier to write good code.

I didn't say that now you should use goto in every piece of code that you write but sparingly when you need it and if the language can help us then why not? now, of course, when you consider the amount of time people use it then indeed, it might not worth the efforts.

Labelled breaks are the opposite: they just offer another way, in addition to the existing one, of writing bad code.

I really don't understand why would you consider label breaks as "bad code" when at rare times it can actually improve the code.

@Korporal
Copy link

Korporal commented Jun 6, 2018

@DavidArno - In your examples you have no further processing after the label outer: so of course its easy to replace the logic as you have with two return statements. So your example seems deliberately contrived and not representative of the general case.

I recall many occasions where an organization had a coding standard "only one entry point and one exit point" and your example is reminiscent of (though not the same as) this kind of rule.

I recall the pain of having to follow that rule even in the parameter validation (the language had no exceptions) with mounting levels of nested if/then/else - ultimately greatly cluttering the code.

Being able to exit a loop at any arbitrary depth is in fact a powerful simplifying mechanism, its intent is obvious "where 100% done looping here" and its effect is clear "continue at the statement following the end of the exited loop".

Granted there are probably many cases where it can be avoided with possibly simpler design but I'm sure there are cases too were being unable to break like this forces the logic to fiddle around unnecessarily.

@Mafii
Copy link

Mafii commented Jun 13, 2018

@Korporal you seem to miss what @DavidArno said about extracting methods for each nested loop.

You can rewrite that code so that is easier to read, refactor and understand. Period. Replacing goto with loop lables doesn't help, it just moves the problem. Refactoring and extracting a method for every loop is the way to go. There are tools for this, but you should be able to do it on your own anyways. It is also less error-prone that nested loops in their raw form, and easier to refactor.

@Korporal
Copy link

Korporal commented Jun 13, 2018

@Mafii - By your reasoning then the existing keywords break and continue are to be avoided in favor of refactoring to avoid such operations. I'm not suggesting goto be used at all, only that break be optionally qualified by a label which must precede some lexically preceding looping construct, as described in the OP.

The existing break keyword can be read as: break current; where current is an imagined label on the current loop so would you advocate that developers avoid using break or continue?

@DavidArno
Copy link

@Korporal,

In your examples you have no further processing after the label outer: so of course its easy to replace the logic as you have with two return statements. So your example seems deliberately contrived and not representative of the general case.

"My" examples are simply @AustinBryan's examples, restructured to avoid the need for breaking out of a nested loop. I don't think Austin's examples are contrived at all.

I recall many occasions where an organization had a coding standard "only one entry point and one exit point" and your example is reminiscent of (though not the same as) this kind of rule.

It's the absolute opposite. In fact such refactoring only really works if that silly rule is ignored.

By your reasoning then the existing keywords breakand continueare to be avoided in favor of refactoring to avoid such operations.

You are missing the point. Folk tend to reach for break all too often. Yet all too often, the code can be simplified by using return. Code after the loop being a case in point. How often does that code contain an if to determine whether break occurred, or the loop ended? By using return, you avoid that extra layer of complexity.

It isn't a case of avoiding break and continue; they are often essential. But at the same time, it's always worth stopping and asking whether a return can be used instead. In my experience, it's only when one tries to break out of two levels at once that alarm bells ring and the code is near guaranteed to need refactoring to simplify it.

A real world example. I went looking for somewhere where I'd used break in my own code and found:

public static Option<T> TrySingle<T>(this IEnumerable<T> collection, Func<T, bool> predicate)
{
    if (predicate == null) throw new ArgumentNullException(nameof(predicate));

    var result = Option<T>.None();
    var count = 0;
    if (collection == null) return result;

    foreach (var element in collection)
    {
        if (!predicate(element)) continue;
        if (++count > 1) break;

        result = Option<T>.Some(element);
    }

    return count == 1 ? result : Option<T>.None();
}

This is an example of what I was talking about before. I test count and break if it's greater than one. Then I re-test count to determine the return value. I can ditch that break though in favour of a return and - in the process - remove the test after the loop:

public static Option<T> TrySingle<T>(this IEnumerable<T> collection, Func<T, bool> predicate)
{
    if (predicate == null) throw new ArgumentNullException(nameof(predicate));

    var result = Option<T>.None();
    var count = 0;
    if (collection == null) return result;

    foreach (var element in collection)
    {
        if (!predicate(element)) continue;
        if (++count > 1) return Option<T>.None();

        result = Option<T>.Some(element);
    }

    return result;
}

A tiny change, yet I've reduced the routes through the code and so reduced it's complexity and thus the likelihood of a bug.

@jez9999
Copy link

jez9999 commented Jun 13, 2018

Considering all the little enhancements they've been introducing to C# (like returning tuples) I think labeled loops with break/continue labels would be a straightforward, useful feature that should be no problem. I'm not sure why this hasn't been done yet.

@Joe4evr
Copy link
Contributor

Joe4evr commented Jun 13, 2018

I'm not sure why this hasn't been done yet.

An LDT member already said this on the matter:

Because the requested change isn't going to happen.

Now, can we please close this thread and move on?

@jez9999
Copy link

jez9999 commented Jun 13, 2018

Yeah I noticed that. It's ridiculous. Is that how C# gets developed, one arrogant guy dictating everything with no justifications?

@DavidArno
Copy link

@jez9999,

The justification is obvious: you can already do this using goto. There are hundreds of new, original ideas that don't just add slightly different syntax for exactly the same thing that are, and should be, in front of this request.

@CyrusNajmabadi
Copy link
Member

Yeah I noticed that. It's ridiculous. Is that how C# gets developed

Yes. The LDM decides what goes in the language. It's not a free-for-all where people just propose things and they just get added :)

Note: if you want, you can always fork the language and do whatever you want with it. However, MS controls the direction and decisions around C#-proper.

@jez9999
Copy link

jez9999 commented Jun 13, 2018

@DavidArno That's not the same, the goto label points to the end of the loop which is much less intuative / easy to read than pointing to the start of the loop. You also can't do a break with that goto; it has to be the equivalent of a continue. You'd need 2 goto labels to choose between a break or a continue.

@Korporal
Copy link

@Joe4evr - But what about the fact that this is (probably) quite easy to add to the language, involves no new radical concepts and potentially rather useful in general, this is what I would call "low hanging fruit" apart from the disagreements here (which reflect tastes) adding this should be pretty straightforward and carry pretty low risks.

@HaloFour
Copy link
Contributor

@jez9999

Two labels to accomplish the same task which is considered relatively rare is not an onerous requirement.

@Korporal

There are tons of small ideas and most of them aren't bad. But an idea has to be sufficiently good to be considered.

@Joe4evr
Copy link
Contributor

Joe4evr commented Jun 13, 2018

the goto label points to the end of thee loop which is much less intuative than pointing to the start of the loop.

I, sincerely, disagree entirely. You intend to jump out of the loop, so pointing to the start of the loop is entirely less intuitive.

There are already good, clean ways to break out of nested loops that you can use:

  • Refactor to a separate method/local function, so you can return out of any level
  • Simply write any boolean logic inside the second for statement:
var found = false; 
for (var i = 0; i < range1 && !found; i++)
for (var j = 0; j < range2 && !found; j++)
    if (i * j == 10)
    {
       found = true;
    }

if (found)
    Console.WriteLine("We found a factor of ten");

Asking for something that actively makes code look worse should not be an option.

@jnm2
Copy link
Contributor

jnm2 commented Jun 15, 2018

@CyrusNajmabadi

Sorry, i broke your post into the two main points i thought were interesting, and i addressed the separately. If you feel like i haven't covered the overall argument you were making, i apologize!

I'm participating in this discussion because I have felt strongly over the years of using goto to simulate labeled breaks that it was an inferior solution, and I have coherent reasons for saying so.
It's demotivating when you say that my logic would say any analyzer is the wrong abstraction level; you're matching my conclusion (which is based on more factors than this one) with a single starting point which is not sufficient for me to reach that conclusion, and pointing out that it's absurd. Of course it is, but it's a bit draining to have to point out that I didn't actually make that absurd argument.

@Korporal
Copy link

@CyrusNajmabadi

I'm really tempted to just write the analyzer at this point.

I'm also really tempted to fork the repo, implement the change and submit a pull request and be done with this rather simple change.

@CyrusNajmabadi
Copy link
Member

I'm also really tempted to fork the repo, implement the change and submit a pull request and be done with this rather simple change.

I would def recommend doing that! That's a core value prop of an open source C#/Vb compiler :)

@jnm2
Copy link
Contributor

jnm2 commented Jun 15, 2018

@HaloFour

That's also partly why I don't think labeling the loop makes sense. Any name you could assign to the loop wouldn't describe why you'd want to interrupt iteration within that loop.

I'm listening, and this isn't the first time I've tried going down this path myself. What happens when there are three reasons for exiting or continuing the outer loop?

@CyrusNajmabadi
Copy link
Member

I'm participating in this discussion because I have felt strongly over the years of using goto to simulate labeled breaks that it was an inferior solution, and I have coherent reasons for saying so.

I def got that! The purpose in my response was to point out that i felt there was a solution that actually addressed your reasons. It felt like you didn't like the solution for reasons unrelated to the solution itself, but because it was through analyzers. I was pointing out then that that argument then seemed to indicate that analyzers themselves weren't really ever viable if used to restrict the available surface area of C#. Did i misinterpret things? If so, again, apologies.

What happens when there are three reasons for exiting or continuing the outer loop?

What would you do with a labeled_loop in that case? Would you have three labels for the loop? Or just one? How would you indicate the reasons for exiting?

@jnm2
Copy link
Contributor

jnm2 commented Jun 15, 2018

@CyrusNajmabadi My argument was that there is an impedence mismatch, and the need for an analyzer was the smallest part of that argument. I'm not saying analyzers are bad.

For example, I'm writing an analyzer to generate and maintain With methods. The need for such an analyzer is one of the reasons I'd ask for a withers feature, but by no means the only reason. That reason can't stand in isolation, but it's a real enough reason when taken with the rest.

What would you do with a labeled_loop in that case? Would you have three labels for the loop? Or just one? How would you indicate the reasons for exiting?

I wouldn't. I'd name the loop. The suggestion to indicate the reason for exiting came from @HaloFour and I am responding to him here, asking the same question you just asked.

@Korporal
Copy link

Korporal commented Jun 15, 2018

@CyrusNajmabadi

What would you do with a labeled_loop in that case? Would you have three labels for the loop? Or just one? How would you indicate the reasons for exiting?

Why is this relevant? We do not currently indicate the "reasons" for doing things in our code other than the code itself perhaps an if (<expression>) for example. The label attached to the loop simply identifies the loop, the "reason" is contained within the logic that leads to the execution of the break or the continue or perhaps a comment!

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Jun 15, 2018

I wouldn't. I'd name the loop.

Ok... so if you'd name the loop... why can you not just label the appropriate jump points if you were to use 'gotos'. I feel like i'm not getting your argument here. :)

@HaloFour
Copy link
Contributor

@jnm2

What happens when there are three reasons for exiting or continuing the outer loop?

Well, for starters, I wouldn't, because that's awful. I don't write Java like that. I don't allow my devs to write Java like that.

But like anything else that can be named I would choose names that fit the logical reason for wanting to do something. isNotApplicable or tryAgain or alreadyFound or whatever. I actually don't know what the naming conventions are for labels or if they differ from locals because I never use them, in either C# or Java.

@CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi My argument was that there is an impedence mismatch, and the need for an analyzer was the smallest part of that argument. I'm not saying analyzers are bad.

Understood. Sorry for trivializing your argument hte away i did.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Jun 15, 2018

Jumping back a bit. I can see the argument for why labeled-loops seem to be desirable. They have the following pros (depending on your perspective).

  1. They seem syntactically/semantically 'attached' to the construct of relevance.
  2. They are not as free-wheeling as 'unrestricted gotos'.
  3. They are a better 'fit' since the primary reason to jump backwards is for looping. So better to just tie this to the loops instead of just tying it to 'arbitrary jumps'.
  4. They are 'clear' (depending on your perspective) because continue/break already make sense for a loop, and people already have to understand it. So this is just saying which loop to continue/break form.

Overall, these are a cogent and clear argument for why such a feature at least "makes sense" in isolation. And, frankly, i'd likely be swayed by this for C# v1 to have this instead of having hte arbitrary-goto feature.

Clearly the above (and likely other stuff i missed) is sufficient for some people to really want this even for a v8+ release. I can accept and respect that.

--

At this point it really comes down to: Is there an LDM member that thinks the above is sufficient? Or is the alternative we have today (gotos + optional-analyzers) reasonably sufficient (and cost effective) to just stick with the status-quo. My personal opinion is "the status quo is fine". However, not everyone may share htat opinion. We'll just have to see if someone wants to champion this.

@jnm2
Copy link
Contributor

jnm2 commented Jun 15, 2018

@CyrusNajmabadi All good, thanks for listening!

Ok... so if you'd name the loop... why can you not just label the appropriate jump points if you were to use 'gotos'. I feel like i'm not getting your argument here. :)

goto next_widget;...next_widget: } versus processing_widgets: for...continue processing_widgets;?

The latter is better documentation of the loop declaration and feels right. The former feels like a hack, is harder to set up and read, and doesn't contain richer information. Possibly less rich.

@Korporal
Copy link

Korporal commented Jun 15, 2018

Labels are just a means to an end a way of identifying the loop to which the break or continue is to be applied. One could avoid labels altogether with a construct like:

break (0); // current loop

or

continue (2); // loop outside the loop outside the current loop

I'm not advocating that of course but do want to point out that a label is not the only way to provide the information to the break or continue.

@CyrusNajmabadi
Copy link
Member

The latter is better documentation of the loop declaration and feels right. The former feels like a workaround with low information.

It's very interesting how different people see these things. I already view a loop as incredibly low-level and imperative. To me your next_widget example is so much clearer and fits far better with how i think about the imperative nature of loops in C-style languages :D

To each their own.

@jnm2
Copy link
Contributor

jnm2 commented Jun 15, 2018

Huh. It's probably also worth pointing out that I'd only find myself doing this if I was inlining for very specific hot paths. Otherwise I'd use .FirstOrDefault. And because the goto thing feels so fragile, half the time I just use a flag which is probably not more readable than the goto.

@HaloFour
Copy link
Contributor

@jnm2

The latter is better documentation of the loop declaration and feels right.

That's my problem with it. It documents the loop declaration, not the reasons for wanting to leave the loop. If you have two reasons for wanting to break, they'd both be accomplished with break processing_widgets which doesn't tell you anything.

@jnm2
Copy link
Contributor

jnm2 commented Jun 15, 2018

@HaloFour That's consistent with how I use continue and break today (not to mention return).

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Jun 15, 2018

@HaloFour That's consistent with how I use continue and break today (not to mention return).

IN that case, i think it's totally reasonable to just do the same with "goto" :)

At this point, i think the arguments have been exhausted. I think i totally get your position @jnm2 . it's consistent and not incorrect in any fashion. It will be up to an LDM member to see if they want to champion.

argue:
    MakePoints();
    MakeCounterPoints();
    goto argue;

Seems totally clear to me :D

@Neme12
Copy link

Neme12 commented Jun 15, 2018

@Korporal

So although the benefit of break label may be relatively small if it's cost is correspondingly small then surely it's worthy of serious consideration on that basis? This whole concept, used in software all the time is often informally described by the term "low hanging fruit".

I suspect this is a small change to the C# compiler code base.

Please note that when we talk about cost, it's not just "how hard is this to implement in the compiler"?
The feature simply may not be worth the added complexity to the language. For example, every feature could potentially clash with another feature you might want to add in the future. But even if that is not a concern, you cannot add new language features willy-nilly just because they might be marginally useful in a few scenarios. You're adding a new feature for every developer or beginner to learn. You're adding more ways to do the same thing and forcing people to understand each style. Language design is really unlike product or even API design. It's simply not a matter of "how hard can this be"? The right question is: Does this provide enough value to justify the added complexity? In my opinion, the answer is no.

I'm also really tempted to fork the repo, implement the change and submit a pull request and be done with this rather simple change.

You're welcome to do that for you own use but please know that if you plan to submit a PR to Roslyn, you likely won't even be allowed to keep it open or have a feature branch unless this feature is championed. If you find it fun to experiment with this or just implement in case it would get considered in the future, that's awesome but don't be disappointed if that doesn't happen.

@Korporal
Copy link

Korporal commented Jun 15, 2018

I certainly would be interested in making such a change but there's clearly a ramp up time and we know the team already has the expertise for this. What I was trying to convey is that it is probably within my abilities despite my limited knowledge of roslyn, unlike some other changes.

I have no idea how the roslyn design ultimately works but one could possibly just implement some form of transformation that transforms this:

            int x = 0;

            my_loop:

            while (true)
            {
                if (x > 10)
                    continue my_loop;

                if (x == 30)
                    break my_loop;
            }

into this:


            int x = 0;

            var my_loop_flag = true;

            my_loop:

            if (my_loop_flag)
            {
                while (true)
                {
                    if (x > 10)
                    {
                        my_loop_flag = true;
                        goto my_loop; // continue my_loop
                    }


                    if (x == 30)
                    {
                        my_loop_flag = false;
                        goto my_loop; // break my_loop
                    }
                }
            }

which is why I described this as syntactic sugar earlier.

@CyrusNajmabadi
Copy link
Member

I have no idea how the roslyn design ultimately works but one could possibly just implement some form of transformation that transforms this:

Yup. That's how roslyn works already today. Indeed, as IL has no concept of loops at all, all loops in roslyn are ultimately "lowered" into just equivalent forms with appropriate "gotos". So you could certainly implement this feature in such a manner.

@Rekkonnect
Copy link
Contributor

I took some time to create the spec for this feature here, if that helps

@333fred
Copy link
Member

333fred commented Sep 30, 2021

This feature is not championed by any member of the LDM. Until that happens, there will be no progress here.

@Richiban
Copy link

Richiban commented Dec 9, 2021

There's a use case I've come across a few times now that's not mentioned here: needing to use break; to leave a loop from inside a switch statement.

Since pattern matching was introduced to the language and keeps improving I find myself using them more and more, but a number of times I've found myself having to write this annoying kludge:

var @break = false;

foreach (var c in input)
{
    switch (c)
    {
        case 'a':
            // snip
            break;
        case 'b':
            // snip
            break;
        case > '0' and < '9':
            // snip
            break;
        case 'c':
            // What I really want to do here is break out of the loop, not the switch statement
            if (anyDigits)
            {
                @break = true;
            }
            
            break;
        default: @break = true; break;
    }
    
    // Now I have to check my boolean to break myself
    if (@break) break;
}

It's possible this particular usecase could get its own feature, such as break@foreach;, but if we're going to go there we might as well include nested loops as well.

@dotnet dotnet locked and limited conversation to collaborators Dec 9, 2021
@333fred 333fred converted this issue into discussion #5525 Dec 9, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests