Uscire da un ciclo annidato


216

Se ho un ciclo for che è nidificato all'interno di un altro, come posso uscire efficacemente da entrambi i loop (interno ed esterno) nel modo più rapido possibile?

Non voglio usare un booleano e poi devo dire andare a un altro metodo, ma solo per eseguire la prima riga di codice dopo il ciclo esterno.

Qual è un modo rapido e piacevole per farlo?

Stavo pensando che le eccezioni non sono economiche / dovrebbero essere gettate solo in una condizione davvero eccezionale, ecc. Quindi non penso che questa soluzione sarebbe buona dal punto di vista delle prestazioni.

Non credo sia giusto sfruttare le nuove funzionalità di .NET (metodi anon) per fare qualcosa che è piuttosto fondamentale.


Volevo solo assicurarmi: perché vuoi farlo?
Jon Limjap,

3
Perché non vuoi usare un booleano? Cosa c'è di sbagliato nel farlo?
Anthony,

In VB.net puoi avvolgere un'istruzione try / finally (nessuna cattura) attorno a un numero arbitrario di loop, quindi "exit try" li uscirà tutti in qualsiasi momento.
Brain2000,

Risposte:


206

Bene, gotoma è brutto e non sempre possibile. È inoltre possibile posizionare i loop in un metodo (o in un metodo anon) e utilizzare returnper tornare al codice principale.

    // goto
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            goto Foo; // yeuck!
        }
    }
Foo:
    Console.WriteLine("Hi");

vs:

// anon-method
Action work = delegate
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits anon-method
        }
    }
};
work(); // execute anon-method
Console.WriteLine("Hi");

Si noti che in C # 7 dovremmo ottenere "funzioni locali", che (sintassi tbd ecc.) Significa che dovrebbe funzionare come:

// local function (declared **inside** another method)
void Work()
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits local function
        }
    }
};
Work(); // execute local function
Console.WriteLine("Hi");

49
In questo tipo di situazione, non penso che usare goto sia peggio del normale uso di qualcosa come break (dopo tutto sono entrambi rami incondizionati di un'etichetta, è solo che con break l'etichetta è implicita).
Greg Beech,

37
a volte goto è meno malvagio delle alternative
seanb

7
@BeowulfOF - break si romperà solo dal loop interno, non dai loop interni ed esterni.
Greg Beech,

36
Andare in sé non è brutto. Ciò che è brutto è l'abuso di goto che si traduce in codice spaghetti. Usare goto per uscire dal loop nidificato è perfettamente ok. Inoltre, nota che tutte le interruzioni, i continui e i ritorni, dal punto di vista della programmazione strutturale, non sono affatto migliori di goto - fondamentalmente sono la stessa cosa, solo in confezioni più belle. Ecco perché i linguaggi strutturali puri (come l'originale Pascal) mancano tutti e tre.
el.pescado,

11
@ el.pescado ha perfettamente ragione: molti programmatori sono stati indotti in errore a credere che gotosia di per sé dannoso , mentre è semplicemente lo strumento giusto per fare alcune cose e come molti strumenti possono essere usati in modo improprio. Questa avversione religiosa contro gotoè francamente abbastanza stupida e decisamente non scientifica.
o0 '.

96

Adattamento C # dell'approccio spesso usato in C - impostare il valore della variabile del loop esterno al di fuori delle condizioni del loop (vale a dire che per il loop usando la variabile int INT_MAX -1è spesso una buona scelta):

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        if (exit_condition)
        {
            // cause the outer loop to break:
            // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
            i = int.MaxValue - 1;
            Console.WriteLine("Hi");
            // break the inner loop
            break;
        }
    }
    // if you have code in outer loop it will execute after break from inner loop    
}

Come dice la nota nel codice break, non passerà magicamente alla prossima iterazione del ciclo esterno - quindi se hai codice al di fuori del ciclo interno questo approccio richiede più controlli. Considera altre soluzioni in tal caso.

Questo approccio funziona con loop fore whilema non funziona foreach. Nel caso in foreachcui non si disponga dell'accesso al codice all'enumeratore nascosto, non è possibile modificarlo (e anche se IEnumeratornon si dispone di un metodo "MoveToEnd").

Ringraziamenti agli autori di commenti incorporati:
i = INT_MAX - 1suggerimento di Meta
for / foreachcommento di ygoe .
Corretto dall'osservazioneIntMax di jmbpiano
sul codice dopo il ciclo interno di blizpasta


@DrG: non funzionerà in c # come "L'istruzione break termina il ciclo di chiusura più vicino o l'istruzione switch in cui appare." (msdn)
blizpasta,

1
@blizpasta So? Se rende falsa la condizione sull'anello esterno (come ha fatto lui), uscirà da entrambi.
Patrick,

51
dovresti usare i = INT_MAX - 1; altrimenti i ++ == INT_MIN <100 e il ciclo continuerà
Meta

6
qualche idea su foreach?
ktutnik,

4
@ktutnik Non funzionerà con foreach perché non avrai accesso al codice all'enumeratore nascosto. Inoltre IEnumerator non ha alcun metodo "MoveToEnd".
giovedì

39

Questa soluzione non si applica a C #

Per le persone che hanno trovato questa domanda tramite altre lingue, Javascript, Java e D consentono interruzioni etichettate e continuano :

outer: while(fn1())
{
   while(fn2())
   {
     if(fn3()) continue outer;
     if(fn4()) break outer;
   }
}

40
è triste, questo non può essere fatto con c #, in così tante volte produrrebbe un codice più pulito.
Rickard,

17
In realtà mi sono emozionato per un secondo fino a quando ho capito che questo NON era per c #. :(
Arvo Bowen,

1
Questo concetto è anche per PowerShell nel caso in cui qualcuno si imbattesse nel problema lì. (Hanno appena messo i due punti davanti al nome dell'etichetta.) Ora so che questo non è specifico di PowerShell ... Questa sintassi sarebbe incompatibile con le etichette goto disponibili in C #. PHP usa qualcos'altro: break 3; Inserisci il numero di livelli dopo l'istruzione break.
giovedì

1
Questo è java non c #
Luca Ziegler

Per coloro che desiderano vedere questa funzione in C #, inserisci i tuoi complimenti qui, per favore 😉 github.com/dotnet/csharplang/issues/869#issuecomment-326606003
m93a

28

Utilizzare una protezione adeguata nell'anello esterno. Posiziona la protezione nell'anello interno prima di rompere.

bool exitedInner = false;

for (int i = 0; i < N && !exitedInner; ++i) {

    .... some outer loop stuff

    for (int j = 0; j < M; ++j) {

        if (sometest) {
            exitedInner = true;
            break;
        }
    }
    if (!exitedInner) {
       ... more outer loop stuff
    }
}

O meglio ancora, astrarre il loop interno in un metodo ed uscire dal loop esterno quando restituisce false.

for (int i = 0; i < N; ++i) {

    .... some outer loop stuff

    if (!doInner(i, N, M)) {
       break;
    }

    ... more outer loop stuff
}

4
Tranne l'OP ha detto "Non voglio usare un booleano".
LeopardSkinPillBoxHat

Molto Pascal-ish ... Probabilmente preferirei usare un goto, anche se di solito li evito come la peste.
Jonathan Leffler,

21

Non citarmi su questo, ma potresti usare goto come suggerito in MSDN. Esistono altre soluzioni, come l'inclusione di un flag che viene verificato in ogni iterazione di entrambi i loop. Finalmente potresti usare un'eccezione come soluzione davvero pesante al tuo problema.

VAI A:

for ( int i = 0; i < 10; ++i ) {
   for ( int j = 0; j < 10; ++j ) {
      // code
      if ( break_condition ) goto End;
      // more code
   }
}
End: ;

Condizione:

bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
   for ( int j = 0; j < 10 && !exit; ++j ) {
      // code
      if ( break_condition ) {
         exit = true;
         break; // or continue
      }
      // more code
   }
}

Eccezione:

try {
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             throw new Exception()
          }
          // more code
       }
    }
catch ( Exception e ) {}

2
queste sono tutte soluzioni alternative caotiche in cui sarebbe super pulito prendere semplicemente in considerazione un metodo e utilizzare il ritorno anticipato
Dustin Getz,

3
:) giusto, questa è una soluzione semplice, ma dovrai passare tutti i dati locali richiesti nel metodo come argomenti ... Questo è uno dei pochi posti in cui goto potrebbe essere la soluzione appropriata
David Rodríguez - dribeas

Il metodo Condition non funziona nemmeno perché "più codice" verrà eseguito una volta dopo essere uscito dal ciclo interno prima di uscire dal ciclo esterno. Il metodo GOTO funziona ma fa esattamente quello che il poster ha detto che non vogliono fare. Il metodo Exception funziona ma è più brutto e più lento di GOTO.
Programmatore di Windows,

Vorrei evidenziare quel punto e virgola dopo l'etichetta. In questo modo quell'etichetta può essere anche alla fine di un blocco. +1
Tamas Hegedus

@Windowsprogrammer L'OP ha chiesto: "Qual è un modo rapido e piacevole di procedere?" Goto è la soluzione preferita: pulita e concisa, senza coinvolgere un metodo separato.
Suncat2000

16

È possibile eseguire il refactoring del loop nidificato in un metodo privato? In questo modo potresti semplicemente "tornare" fuori dal metodo per uscire dal ciclo.


Con il vantaggio secondario di abbreviare il metodo originale :-)
Martin Capodici,

C ++ 11 lambda rendono questo facile per alcuni casi:[&] { ... return; ... }();
BCS

14

Mi sembra che alla gente non piaccia molto gotoun'affermazione, quindi ho sentito il bisogno di raddrizzare un po '.

Credo che le "emozioni" delle persone alla gotofine si riducano alla comprensione del codice e (idee sbagliate) sulle possibili implicazioni delle prestazioni. Prima di rispondere alla domanda, entrerò quindi in alcuni dettagli su come viene compilata.

Come tutti sappiamo, C # viene compilato in IL, che viene quindi compilato in assembler usando un compilatore SSA. Darò un po 'di spunti su come tutto questo funziona, e quindi proverò a rispondere alla domanda stessa.

Da C # a IL

Per prima cosa abbiamo bisogno di un pezzo di codice C #. Cominciamo semplice:

foreach (var item in array)
{
    // ... 
    break;
    // ...
}

Lo farò passo dopo passo per darti una buona idea di cosa succede sotto il cofano.

Prima traduzione: da foreachal forciclo equivalente (Nota: sto usando un array qui, perché non voglio entrare nei dettagli di IDisposable - nel qual caso dovrei anche usare un IEnumerable):

for (int i=0; i<array.Length; ++i)
{
    var item = array[i];
    // ...
    break;
    // ...
}

Seconda traduzione: fore breakè tradotto in un equivalente più semplice:

int i=0;
while (i < array.Length)
{
    var item = array[i];
    // ...
    break;
    // ...
    ++i;
}

E terza traduzione (questo è l'equivalente del codice IL): cambiamo breake whilein un ramo:

    int i=0; // for initialization

startLoop:
    if (i >= array.Length) // for condition
    {
        goto exitLoop;
    }
    var item = array[i];
    // ...
    goto exitLoop; // break
    // ...
    ++i;           // for post-expression
    goto startLoop; 

Mentre il compilatore fa queste cose in un solo passaggio, ti dà un'idea del processo. Il codice IL che si evolve dal programma C # è la traduzione letterale dell'ultimo codice C #. Puoi vederlo qui: https://dotnetfiddle.net/QaiLRz (fai clic su 'visualizza IL')

Ora, una cosa che hai osservato qui è che durante il processo, il codice diventa più complesso. Il modo più semplice per osservarlo è il fatto che avevamo bisogno di sempre più codice per realizzare la stessa cosa. Si potrebbe anche sostenere che foreach, for, whilee breaksono in realtà corto mani goto, che è in parte vero.

Da IL a Assembler

Il compilatore .NET JIT è un compilatore SSA. Non entrerò in tutti i dettagli del modulo SSA qui e su come creare un compilatore ottimizzante, è troppo, ma posso dare una comprensione di base di ciò che accadrà. Per una comprensione più profonda, è meglio iniziare a leggere sull'ottimizzazione dei compilatori (questo libro mi piace per una breve introduzione: http://ssabook.gforge.inria.fr/latest/book.pdf ) e LLVM (llvm.org) .

Ogni compilatore ottimizzato si basa sul fatto che il codice è semplice e segue schemi prevedibili . Nel caso dei loop FOR, utilizziamo la teoria dei grafi per analizzare i rami e quindi ottimizzare cose come i cicli nei nostri rami (ad esempio rami all'indietro).

Tuttavia, ora abbiamo filiali dirette per implementare i nostri loop. Come avrai intuito, questo è in realtà uno dei primi passi che la JIT sta per risolvere, in questo modo:

    int i=0; // for initialization

    if (i >= array.Length) // for condition
    {
        goto endOfLoop;
    }

startLoop:
    var item = array[i];
    // ...
    goto endOfLoop; // break
    // ...
    ++i;           // for post-expression

    if (i >= array.Length) // for condition
    {
        goto startLoop;
    }

endOfLoop:
    // ...

Come puoi vedere, ora abbiamo un ramo all'indietro, che è il nostro piccolo anello. L'unica cosa che è ancora brutta qui è il ramo con cui siamo finiti a causa della nostra breakdichiarazione. In alcuni casi, possiamo spostarlo allo stesso modo, ma in altri è lì per rimanere.

Quindi perché il compilatore fa questo? Bene, se possiamo srotolare il loop, potremmo essere in grado di vettorializzarlo. Potremmo anche essere in grado di provare che sono state aggiunte solo costanti, il che significa che tutto il nostro circuito potrebbe svanire nel nulla. Riassumendo: rendendo prevedibili gli schemi (rendendo prevedibili i rami), possiamo provare che certe condizioni restano nel nostro ciclo, il che significa che possiamo fare magie durante l'ottimizzazione JIT.

Tuttavia, i rami tendono a spezzare quei bei modelli prevedibili, il che è qualcosa di ottimizzatore quindi una specie di antipatia. Break, continue, goto - tutti hanno intenzione di rompere questi schemi prevedibili - e quindi non sono davvero "carini".

A questo punto dovresti anche capire che un semplice foreachè più prevedibile di un mucchio di gotoaffermazioni che vanno dappertutto. In termini di (1) leggibilità e (2) dal punto di vista dell'ottimizzatore, è sia la soluzione migliore.

Un'altra cosa degna di nota è che è molto importante per l'ottimizzazione dei compilatori di assegnare i registri alle variabili (un processo chiamato allocazione dei registri ). Come forse saprai, c'è solo un numero finito di registri nella tua CPU e sono di gran lunga i pezzi di memoria più veloci nel tuo hardware. Le variabili utilizzate nel codice che si trova nel ciclo più interno, hanno maggiori probabilità di ottenere un registro assegnato, mentre le variabili al di fuori del ciclo sono meno importanti (perché questo codice è probabilmente colpito meno).

Aiuto, troppa complessità ... cosa devo fare?

La linea di fondo è che dovresti sempre usare i costrutti linguistici che hai a tua disposizione, che di solito (implicitamente) costruiranno modelli prevedibili per il tuo compilatore. Cercate di evitare strani rami, se possibile (in particolare: break, continue, gotoo returnin mezzo al nulla).

La buona notizia qui è che questi schemi prevedibili sono sia facili da leggere (per gli umani) sia facili da individuare (per i compilatori).

Uno di questi schemi si chiama SESE, che sta per Single Entry Single Exit.

E ora arriviamo alla vera domanda.

Immagina di avere qualcosa del genere:

// a is a variable.

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...

     if (i*j > a) 
     {
        // break everything
     }
  }
}

Il modo più semplice per rendere questo un modello prevedibile è semplicemente eliminare ifcompletamente:

int i, j;
for (i=0; i<100 && i*j <= a; ++i) 
{
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
}

In altri casi puoi anche dividere il metodo in 2 metodi:

// Outer loop in method 1:

for (i=0; i<100 && processInner(i); ++i) 
{
}

private bool processInner(int i)
{
  int j;
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
  return i*j<=a;
}

Variabili temporanee? Buono, cattivo o brutto?

Potresti anche decidere di restituire un booleano all'interno del loop (ma preferisco personalmente il modulo SESE perché è così che il compilatore lo vedrà e penso che sia più pulito da leggere).

Alcune persone pensano che sia più pulito usare una variabile temporanea e propongono una soluzione come questa:

bool more = true;
for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j) 
  {
     // ...
     if (i*j > a) { more = false; break; } // yuck.
     // ...
  }
  if (!more) { break; } // yuck.
  // ...
}
// ...

Personalmente sono contrario a questo approccio. Guarda di nuovo su come viene compilato il codice. Ora pensa a cosa farà con questi schemi piacevoli e prevedibili. Prendi la foto?

Bene, fammi precisare. Quello che accadrà è che:

  • Il compilatore scriverà tutto come rami.
  • Come fase di ottimizzazione, il compilatore eseguirà l'analisi del flusso di dati nel tentativo di rimuovere la strana morevariabile che viene utilizzata solo nel flusso di controllo.
  • In caso di successo, la variabile moreverrà eliminata dal programma e restano solo i rami. Questi rami saranno ottimizzati, quindi otterrai un solo ramo dal ciclo interno.
  • In caso di esito negativo, la variabile moreviene sicuramente utilizzata nel ciclo più interno, quindi se il compilatore non la ottimizza, ha un'alta probabilità di essere allocata in un registro (che consuma preziosa memoria del registro).

Quindi, per riassumere: l'ottimizzatore nel tuo compilatore avrà un sacco di problemi per capire che moreviene utilizzato solo per il flusso di controllo e, nel migliore dei casi, lo tradurrà in un singolo ramo al di fuori dell'esterno per ciclo continuo.

In altre parole, lo scenario migliore è che finirà con l'equivalente di questo:

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...
     if (i*j > a) { goto exitLoop; } // perhaps add a comment
     // ...
  }
  // ...
}
exitLoop:

// ...

La mia opinione personale su questo è abbastanza semplice: se questo è ciò che intendevamo da sempre, rendiamo il mondo più facile sia per il compilatore che per la leggibilità, e scriviamo subito.

tl; dr:

Linea di fondo:

  • Usa una semplice condizione nel tuo ciclo for se possibile. Attenersi il più possibile ai costrutti linguistici di alto livello che avete a vostra disposizione.
  • Se tutto fallisce e rimani con uno gotoo bool more, preferisci il primo.

OTOH: Raramente ho il desiderio di rompere i loop esterni o desidero goto-equiv (e un sacco di codice che probabilmente potrebbe essere scritto in modo più pulito), anche se potrebbe essere più comune in altri domini .. oggi è stato un caso, ma ciò è dovuto in gran parte a yield return. Cioè, mentre è facile mostrare che ci sono alcuni casi utili, per la maggior parte del codice "a livello di applicazione" è probabilmente un evento molto basso di frustrazione con C #, escludendo quelli "appena provenienti da" C / C ++ ;-) Mi manca anche occasionalmente Il "lancio" di Ruby (svolgimento senza eccezioni) in alcune occasioni perché si adatta anche a questo dominio.
user2864740

1
@ Suncat2000 Basta introdurre più codice come variabili e rami condizionali per evitare di usare a goto, non rende il tuo codice più leggibile - direi che ciò che accade è esattamente l'opposto: in effetti il ​​tuo flusso di controllo conterrà più nodi - il che è praticamente la definizione di complessità. Basta evitarlo per aiutare i suoni di autodisciplina controproducenti in termini di leggibilità.
atlaste

@atlaste: scusami, lasciami esprimere il mio commento più chiaramente. Quello che avrei dovuto dire era: bella spiegazione. Ma le persone che evitano il goto non riguardano le prestazioni; si tratta di evitare gli abusi che causano la mancanza di leggibilità. Possiamo presumere che le persone che evitano gotosemplicemente non hanno la disciplina per non abusarne.
Suncat2000,

@atlaste, che lingua è "ackomplish"?
Pacerier,

11

fattore in una funzione / metodo e utilizzare il ritorno anticipato o riorganizzare i loop in una clausola while. vai / eccezioni / qualunque cosa non sia certamente appropriata qui.

def do_until_equal():
  foreach a:
    foreach b:
      if a==b: return

10

Hai chiesto una combinazione di veloce, carino, senza usare un booleano, senza usare goto e C #. Hai escluso tutti i modi possibili di fare quello che vuoi.

Il modo più rapido e meno brutto è usare un goto.


Completamente d'accordo. Introdurre un nuovo metodo solo per sbarazzarsi di un singolo goto è sciocco. Se il compilatore non è in grado di incorporare tale chiamata di metodo per qualsiasi motivo, finiremo con un sovraccarico completamente inutile di una chiamata di metodo aggiuntiva. Generare e catturare eccezioni solo per interrompere il ciclo è allo stesso tempo più prolisso e ridicolmente più costoso.
Zar Shardan,

@Windowsprogramm: OP non ha chiesto "nessun uso di goto". Non voleva "passare a un altro metodo". La domanda era lungi dall'escludere tutti i modi possibili, ma hai ragione a implicare che Goto è il migliore qui.
Suncat2000

5

A volte è bello astrarre il codice nella sua stessa funzione e usare un ritorno anticipato, anche se i rendimenti iniziali sono malvagi:)

public void GetIndexOf(Transform transform, out int outX, out int outY)
{
    outX = -1;
    outY = -1;

    for (int x = 0; x < Columns.Length; x++)
    {
        var column = Columns[x];

        for (int y = 0; y < column.Transforms.Length; y++)
        {
            if(column.Transforms[y] == transform)
            {
                outX = x;
                outY = y;

                return;
            }
        }
    }
}

1
ma ciò consente la soluzione tradizionale richiesta dall'OP
Surya Pratap il

2

Ho visto molti esempi che usano "break" ma nessuno che usa "continue".

Richiederebbe ancora una bandiera di qualche tipo nel circuito interno:

while( some_condition )
{
    // outer loop stuff
    ...

    bool get_out = false;
    for(...)
    {
        // inner loop stuff
        ...

        get_out = true;
        break;
    }

    if( get_out )
    {
        some_condition=false;
        continue;
    }

    // more out loop stuff
    ...

}

1

Da quando ho visto per la prima volta breakin C un paio di decenni fa, questo problema mi ha irritato. Speravo che qualche miglioramento del linguaggio avrebbe avuto un'estensione da interrompere che avrebbe funzionato così:

break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it's parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.

3
Quindi un programmatore di manutenzione inserirà un altro livello di annidamento, risolverà alcune delle dichiarazioni di interruzione e interromperà alcune delle altre dichiarazioni di interruzione. La soluzione per questo è invece quella di passare a un'etichetta. Questo è stato davvero proposto, ma i pragmatici usano invece andare a un'etichetta.
Programmatore di Windows,

Aspetta, chi fa più la programmazione di manutenzione? :)
Jesse C. Slicer,

2
JavaScript ha persino etichettato blocchi / istruzioni break. devguru.com/Technologies/ecmascript/quickref/break.html
David Grant il

@Chris Bartow: fantastico! ha reso il mio Natale :) @David Grant: così sembra JS break == C sta andando?
Jesse C. Slicer,

D ha etichettato break / continue
BCS

0

Ricordo dai miei giorni da studente che è stato detto che è matematicamente dimostrabile che puoi fare qualsiasi cosa nel codice senza un goto (cioè non c'è situazione in cui goto è l'unica risposta). Quindi non uso mai i goto (solo le mie preferenze personali, non suggerendo che ho ragione o torto)

Ad ogni modo, per uscire dai loop nidificati faccio qualcosa del genere:

var isDone = false;
for (var x in collectionX) {
    for (var y in collectionY) {
        for (var z in collectionZ) {
            if (conditionMet) {
                // some code
                isDone = true;
            }
            if (isDone)
                break;
        }
        if (isDone) 
            break;
    }
    if (isDone)
        break;
}

... spero che ciò aiuti per coloro che come me sono "fanboy" anti-goto :)


Mi dispiace dirtelo, ma il tuo conferenziere è l'unico responsabile per le tue condizioni. Se si fosse preso la briga di costringerti a imparare l'assemblaggio, allora sai che 'goto' è solo un salto (ovviamente sto ignorando il fatto che questa è una domanda #).
cmroanirgo,

0

È così che l'ho fatto. Ancora una soluzione alternativa.

foreach (var substring in substrings) {
  //To be used to break from 1st loop.
  int breaker=1;
  foreach (char c in substring) {
    if (char.IsLetter(c)) {
      Console.WriteLine(line.IndexOf(c));
      \\setting condition to break from 1st loop.
      breaker=9;
      break;
    }
  }
  if (breaker==9) {
    break;
  }
}


0

Il modo più semplice per terminare un doppio loop sarebbe terminare direttamente il primo loop

string TestStr = "The frog jumped over the hill";
char[] KillChar = {'w', 'l'};

for(int i = 0; i < TestStr.Length; i++)
{
    for(int E = 0; E < KillChar.Length; E++)
    {
        if(KillChar[E] == TestStr[i])
        {
            i = TestStr.Length; //Ends First Loop
            break; //Ends Second Loop
        }
    }
}

Questo non funziona in quanto usando l'interruzione finirai il ciclo interno. Il ciclo esterno continuerà a scorrere.
Alex Leo,

0

I loop possono essere interrotti utilizzando condizioni personalizzate nel loop, consentendo di avere un codice pulito.

    static void Main(string[] args)
    {
        bool isBreak = false;
        for (int i = 0; ConditionLoop(isBreak, i, 500); i++)
        {
            Console.WriteLine($"External loop iteration {i}");
            for (int j = 0; ConditionLoop(isBreak, j, 500); j++)
            {
                Console.WriteLine($"Inner loop iteration {j}");

                // This code is only to produce the break.
                if (j > 3)
                {
                    isBreak = true;
                }                  
            }

            Console.WriteLine("The code after the inner loop will be executed when breaks");
        }

        Console.ReadKey();
    }

    private static bool ConditionLoop(bool isBreak, int i, int maxIterations) => i < maxIterations && !isBreak;   

Con questo codice otteniamo il seguente output:

  • Iterazione loop esterno 0
  • Iterazione del loop interno 0
  • Iterazione del loop interno 1
  • Iterazione del loop interno 2
  • Iterazione dell'anello interno 3
  • Iterazione del loop interno 4
  • Il codice dopo il ciclo interno verrà eseguito in caso di interruzioni

0

Un'altra opzione è una funzione anonima auto-invocata . No goto, nessuna etichetta, nessuna nuova variabile, nessun nuovo nome-funzione usato e una riga più corta dell'esempio di metodo anonimo in alto.

// self-invoked-anonymous-function
new Action(() =>
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits self invoked lambda expression
        }
    }
})();
Console.WriteLine("Hi");

-3

Genera un'eccezione personalizzata che esce dal ciclo esterno.

Funziona per for, foreacho per whilequalsiasi tipo di loop e qualsiasi linguaggio che utilizza il try catch exceptionblocco

try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}

-4
         bool breakInnerLoop=false
        for(int i=0;i<=10;i++)
        {
          for(int J=0;i<=10;i++)
          {
              if(i<=j)
                {
                    breakInnerLoop=true;
                    break;
                }
          }
            if(breakInnerLoop)
            {
            continue
            }
        }

Qual è la differenza essenziale con la risposta di dviljoen?
Gert Arnold,

Ciò non funzionerà, perché non si controlla la condizione "breakInnerLoop" nel ciclo esterno per, quindi si passa al ciclo successivo, inoltre si sono scritti j e J e si è perso un punto e virgola, che non verrà compilato.
Sebastian,

-4

Dal momento che vedo che hai accettato la risposta in cui la persona ti rimanda alla dichiarazione goto, dove nella programmazione moderna e nell'opinione degli esperti goto è un assassino, lo abbiamo chiamato un assassino nella programmazione che ha alcuni motivi, di cui non ne discuterò qui a questo punto, ma la soluzione della tua domanda è molto semplice, puoi usare una bandiera booleana in questo tipo di scenario come lo dimostrerò nel mio esempio:

            for (; j < 10; j++)
            {
                //solution
                bool breakme = false;
                for (int k = 1; k < 10; k++)
                {
                   //place the condition where you want to stop it
                    if ()
                    {
                        breakme = true;
                        break;
                    }
                }

                if(breakme)
                    break;
               }

semplice e chiaro. :)


leggi la domanda prima di suggerire una pausa. Da qui i voti negativi.
cmroanirgo,

1
leggilo ora, ma quando ho pubblicato questa risposta non è stata aggiunta la versione modificata del non utilizzo booleano, ecco perché ho pubblicato questa risposta .. ma grazie per il downvote!
Steve

1
Non è una cosa personale. Sfortunatamente, il voto è ora bloccato;) È una parte necessaria di SO per ottenere le migliori risposte all'inizio (cosa che non sempre accade)
cmroanirgo,

-6

Hai anche guardato la breakparola chiave? oo

Questo è solo uno pseudo-codice, ma dovresti essere in grado di vedere cosa intendo:

<?php
for(...) {
    while(...) {
        foreach(...) {
            break 3;
        }
    }
}

Se pensi di breakessere una funzione comebreak() , allora il parametro sarebbe il numero di loop da cui uscire. Dato che siamo nel terzo ciclo del codice qui, possiamo uscire da tutti e tre.

Manuale: http://php.net/break


13
Non è una domanda php.
Zerga,

-10

Penso che a meno che tu non voglia fare la "cosa booleana" l'unica soluzione è in realtà lanciare. Che ovviamente non dovresti fare ..!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.