레이블 지정 break 및 continue 문

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련된 언어 디자인 모임(LDM) 노트에 기록되어 있습니다.

C# 언어 표준에 기능 스펙렛을 채택하는 과정에 대해 사양 문서에서 더 자세히 알아볼 수 있습니다.

요약

허용 break 및 continue 문은 필요에 따라 대상으로 지정할 루프 또는 switch 문을 식별하는 레이블을 지정하여 문을 요구하지 goto 않고 중첩된 구문 또는 중첩 함수, 튜플 반환 등과 같은 기타 왜곡에서 보다 명확한 제어 흐름을 사용하도록 설정합니다.

동기

문을 포함하는 switch 중첩된 루프 또는 루프를 사용하는 경우 개발자는 내부 컨텍스트 내에서 외부 루프를 중단하거나 계속해야 하는 경우가 많습니다. 현재 이 작업을 수행하는 두 가지 주요 방법이 있으며, 둘 다 상당한 단점이 있습니다.

문 사용 goto

string foundValue = null;
for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        foundValue = GetValue(x, y);
        if (foundValue == target)
            goto FOUND;
    }
}
FOUND:
ProcessValue(foundValue);

작동하는 동안 goto 루프 구문 후에 레이블을 배치해야 하며 특정 루프에서 중단하려는 의도를 명확하게 전달하지는 않습니다. 외부 루프를 계속하기 위해 이 접근 방식은 더욱 어색해집니다.

for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            goto CONTINUE_OUTER;
    }
    CONTINUE_OUTER: ;
}

증분 및 조건 검사가 수행되도록 닫는 중괄호 바로 앞에 레이블을 루프 본문의 끝에 배치해야 하기 때문에 이 패턴은 혼란스럽습니다. 둘 다 break 동일한 continue 외부 루프에 필요한 경우 두 개의 별도 레이블이 필요합니다.

for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            goto CONTINUE_OUTER;
        
        if (ShouldExitAll(x, y))
            goto BREAK_OUTER;
    }
    CONTINUE_OUTER: ;
}
BREAK_OUTER:
// Subsequent statements

플래그 변수 사용

string foundValue = null;
bool shouldBreak = false;
for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        foundValue = GetValue(x, y);
        if (foundValue == target)
        {
            shouldBreak = true;
            break;
        }
    }
    if (shouldBreak)
        break;
}
ProcessValue(foundValue);

이 방법을 사용하려면 추가 상태 관리가 필요하고 코드 세부 정보를 늘리며 제어 흐름 의도를 모호하게 합니다.

제안된 솔루션

레이블이 지정되고 breakcontinue코드가 더 명확해지고 유지 관리가 더 쉬워집니다.

string foundValue = null;
outer: for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        foundValue = GetValue(x, y);
        if (foundValue == target)
            break outer;
    }
}
ProcessValue(foundValue);

레이블은 식별되는 루프에 직접 배치되며 break/continue 문은 대상의 이름을 명시적으로 지정합니다. 계속하려면 다음을 수행합니다.

outer: for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            continue outer;
    }
}

이렇게 하면 레이블 배치가 혼동되지 않고 "외부 루프 계속"을 goto자연스럽게 표현합니다. 두 작업 모두에 단일 레이블을 사용할 수 있습니다.

outer: for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            continue outer;
        
        if (ShouldExitAll(x, y))
            break outer;
    }
}

이 기능은 C# 커뮤니티에서 광범위하게 요청되었으며, 토론은 수십 년 전으로 거슬러 올라간다. 유사한 기능은 다른 여러 최신 언어로 존재합니다.

이러한 모든 경우 언어는 이 사양과 동일한 방식으로 작동합니다. 즉, 일부 구문에는 레이블이 있을 수 있으며 해당 또는 문에서 해당 레이블을 continuebreak 참조할 수 있습니다.

상세 디자인

다음 업데이트는 C# 7 표준(statements.md)의 해당 섹션에 대한 차이로 표시됩니다. 이 섹션 전체에서 취소선 은 기존 사양에서 제거되는 텍스트를 나타내고 굵게 는 추가되는 텍스트를 나타냅니다. 변경되지 않은 산문은 컨텍스트에 대해 축어로 따옴표로 묶습니다.

§13.5 레이블이 지정된 문

기존 단락 바로 뒤에 다음 단락을 삽입합니다. "레이블 범위 내의 문(§13.10.4)에서 goto 레이블을 참조할 수 있습니다.":

labeled_statement 내에 즉시 중첩된 문이switch_statement(§13.8.3) 또는 iteration_statement(§13.9)인 경우 중첩된 문은 labeled_statement식별자를사용하여 레이블이 지정됩니다. break_statement(§13.10.2) 또는 continue_statement(§13.10.3)는 레이블이 지정된 문을 참조할 식별자를 지정할 수 있습니다.

참고: labeled_statement내에 즉시 중첩된 문만 해당 식별자를 사용하여 레이블이 지정됩니다. 예를 들어 지정된 경우 a: b: while (…) …iteration_statementa 레이블만 b 지정하고 switch_statement 또는iteration_statementb: while (…) … 아닌 내부 labeled_statement 레이블을 지정합니다. 따라서 break a;continue a; 루프 본문 내에 나타나는 것이 문을 대상으로 while 하지 않습니다. 끝 메모

§13.10.2 break 문

break_statement
    : 'break' identifier? ';'
    ;

문은 break 가장 가까운 바깥쪽switch, while, do또는 forforeach 문을 종료합니다.

이 문은 break 가장 가까운 바깥쪽 switch_statement (§13.8.3) 또는 iteration_statement (§13.9)을 종료하거나, 식별자가 지정된 경우 가장 가까운 바깥쪽 switch_statement 또는 iteration_statement해당 식별자 ( §13.5 참조)를 종료합니다.

문의 대상 break 은 위와 같이 가장 가까운 바깥쪽switch, while, do또는 forforeach 문 문의 끝점입니다. 문이 , break , while, dofor또는 foreach 문으로 switch묶이지 않으면 컴파일 시간 오류가 발생합니다.이러한 묶은 문이 없으면 컴파일 시간 오류가 발생합니다.

여러 switch, while, do, for, 또는 foreach 문이 서로 중첩되는 경우, break 문은 가장 안쪽 문장에만 적용됩니다. 여러 중첩 수준에서 goto 제어를 전송하기 위해 문(§13.10.4)을 사용해야 합니다.

break 구문은 finally 블록을 종료할 수 없습니다(§13.11). break 문이 finally 블록 내에서 발생하면, break 문의 대상은 반드시 동일한 finally 블록 내에 있어야 합니다. 그렇지 않으면 컴파일 시 오류가 발생합니다.

break 문은 다음과 같이 실행됩니다.

  • break 문이 하나 이상의 try 블록과 이에 연결된 finally 블록을 종료하는 경우, 컨트롤은 처음에 가장 안쪽 finally 문의 try 블록으로 전송됩니다. 컨트롤이 finally 블록의 끝점에 도달할 때, 컨트롤은 다음 바깥의 finally 문장의 try 블록으로 전송됩니다. 이 프로세스는 모든 중간 finally 문의 블록이 실행될 때까지 try 반복됩니다.
  • 제어는 break 문장의 대상으로 이동됩니다.

break 문이 무조건 제어를 다른 곳으로 전송하기 때문에 break 문 끝점이 결코 도달하지 않습니다.

예: 레이블이 가장 break 가까운 바깥쪽 switch_statement 확인되거나 레이블이 일치하는 iteration_statement 확인됩니다.

outer: for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 10; j++)
    {
        if (i * j > 20)
            break outer; // exits the outer for-loop
    }
}

끝 예제

§13.10.3 continue 문

continue_statement
    : 'continue' identifier? ';'
    ;

문은 continue 가장 가까운 바깥쪽, do또는 forforeach 문의 새 반복을 while시작합니다.

이 문은 continue 가장 가까운 바깥쪽 iteration_statement(§13.9)의 새 반복을 시작하거나, 식별자가 지정된 경우 해당 식별자가 레이블이 지정된 가장 가까운 바깥쪽 iteration_statement 시작합니다(§13.5 참조).

문의 대상 continue 은 위와 같이 결정된 가장 가까운 바깥쪽while, dofor또는 foreach 문 iteration_statement 포함된 문의 끝점입니다. 문이 , continuedo, for또는 foreach 문으로 while묶이지 않으면 컴파일 시간 오류가 발생합니다.이러한 묶은 문이 없으면 컴파일 시간 오류가 발생합니다.

여러 while, do, for, 또는 foreach 문이 서로 중첩된 경우, continue 문은 가장 안쪽 문에만 적용됩니다. 여러 중첩 수준에서 goto 제어를 전송하기 위해 문(§13.10.4)을 사용해야 합니다.

continue 구문은 finally 블록을 종료할 수 없습니다(§13.11). continue 문이 finally 블록 내에서 발생하면, continue 문의 대상은 반드시 동일한 finally 블록 내에 있어야 합니다. 그렇지 않으면 컴파일 시 오류가 발생합니다.

continue 문은 다음과 같이 실행됩니다.

  • continue 문이 하나 이상의 try 블록과 이에 연결된 finally 블록을 종료하는 경우, 컨트롤은 처음에 가장 안쪽 finally 문의 try 블록으로 전송됩니다. 컨트롤이 finally 블록의 끝점에 도달할 때, 컨트롤은 다음 바깥의 finally 문장의 try 블록으로 전송됩니다. 이 프로세스는 모든 중간 finally 문의 블록이 실행될 때까지 try 반복됩니다.
  • 제어는 continue 문장의 대상으로 이동됩니다.

continue 문이 무조건 제어를 다른 곳으로 전송하기 때문에 continue 문 끝점이 결코 도달하지 않습니다.

예: 레이블이 지정된 continue 경우 레이블이 일치하는 가장 가까운 바깥쪽 iteration_statement 확인됩니다.

outer: for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 10; j++)
    {
        if (ShouldSkip(i, j))
            continue outer; // continues the outer for-loop
    }
}

끝 예제

단점/대안

문을 계속 사용 goto

C#은 동일한 제어 흐름을 수행할 수 있는 이미 지원합니다 goto. 그러나 goto 레이블이 지정된 중단/계속에 비해 몇 가지 단점이 있습니다.

  • 중단 및 계속 시나리오에 대해 별도의 레이블이 필요합니다(중단 레이블은 루프 후 이동, 레이블은 닫는 중괄호 앞에 계속 이동)
  • 레이블 배치는 직관적이지 않으며 중단 여부 또는 계속 진행 여부에 따라 다릅니다.
  • 의도에 대해 덜 명시적(위치로 이동 및 특정 루프 중단/연속)
  • 부서지기 쉽고 오류가 발생하기 쉽습니다. 개발자는 레이블과 해당 대상 구문 사이에 문이 실수로 배치되지 않도록 해야 합니다. 예를 들어 goto END_LOOP; 다음에 이어 END_LOOP:유지 관리 중에 실수로 문을 삽입하여 의도한 제어 흐름을 깨뜨리기 쉽습니다. 레이블이 지정된 루프는 레이블을 구문에 직접 바인딩하여 이 문제를 방지합니다.
  • 휴식/계속 방지라는 레이블이 지정된 역사적 오명을 전달합니다.

플래그 변수 사용

동기 부여 섹션에 표시된 것처럼 플래그 변수는 작동하지만 중요한 상용구가 추가되고 제어 흐름 논리가 모호해집니다.

숫자 수준 사용 break N 또는 continue N 사용

  • 리팩터링 중에 취약합니다(루프 수준을 추가/제거하려면 모든 숫자 참조를 업데이트해야 합니다.)
  • 읽기가 더 어렵습니다(대상을 이해하려면 수준을 계산해야 합니다.)
  • 명명된 레이블보다 덜 명시적
  • 명확성 부족 (1 기반? 0 기반?)

별도의 메서드로 리팩터링

이는 종종 모범 사례이지만 항상 가능하거나 적절한 것은 아니며 때로는 간단한 제어 흐름이 되어야 하는 것에 불필요한 복잡성을 발생시킬 수 있습니다.

이 제안은 다음 커뮤니티 토론을 통합하고 해결합니다.

질문 열기

레이블 의미 체계

현재 사양은 해당 식별자로 레이블이 지정된 가장 안쪽에 적용 가능한 루프/스위치 구문을 찾은 다음 표준 break/continue 의미 체계를 사용하여 디스패치하기 위한continue identifierbreak identifier/의미 체계를 정의합니다. 대신 'goto'와 동일한 규칙을 사용하여 레이블을 식별한다고 break identifier/continue identifier 말하는 것이 대체 공식화입니다. 레이블에 중단/계속을 묶는 루프/스위치 구문이 직접 포함된 경우 중단/계속이 적용되는 루프/스위치입니다.

두 형식화 모두 사실상 동일하므로 동일한 프로그램 집합을 허용하고 허용하지 않습니다. 이 스펙렛에서 선택한 접근 방식은 개념적 및 리터럴 단순성을 위해 수행되었습니다. 레이블의 범위 지정 또는 식별자의 goto 바인딩을 처리하거나 루프/스위치 및 continue/break 문을 확인하기 위한 바깥쪽 바인딩 논리를 정의할 필요가 없습니다. 대신 중단/계속이 지정된 경우 적절한 바깥쪽 루프/스위치를 찾는 간단한 사양 언어를 확장하여 가장 안쪽을 지나 그 이상으로 확장할 수 있습니다.

LDM이 goto+label 의미 체계에 더 단단히 묶인다고 느끼면 사양을 조정하는 것이 어렵지 않을 것입니다. 그룹이 후자의 형태가 여기에 찍은 형태보다 더 자연스레 느끼는 경우이 질문을 열어 두십시오.

void M()
{
  label:
  Console.WriteLine();
  
  foreach (var x in ...)
  {
    break label;
    // should this scenario fail because:
    // 1. the identifier lookup fails, or
    // 2. the label is rejected (not a valid label for a `break` since not attached to a loop construct) after being found?
  }
}

레이블을 선언하는 방법에 대한 사양:

각 블록 또는 switch_block 레이블에 대한 별도의 선언 공간을 만듭니다. 이름은 labeled_statements 통해 이 선언 공간에 도입되고 이름은 goto_statements 통해 참조됩니다. 블록의 레이블 선언 공간에는 중첩된 블록이 포함됩니다. 따라서 중첩된 블록 내에서는 바깥쪽 블록의 레이블과 이름이 같은 레이블을 선언할 수 없습니다.

레이블이 지정된 문의 사양:

레이블의 범위는 중첩된 블록을 포함하여 레이블이 선언되는 전체 블록입니다. 이름이 같은 두 레이블에 겹치는 범위가 있는 경우 컴파일 시간 오류입니다.
레이블 범위 내의 goto 문(§13.10.4)에서 레이블을 참조할 수 있습니다.

중첩된 레이블

지원되어야 하나 a: b: while (true) continue a; 요?

권장 사항: 아니요. 어떤 사용자도 이를 요청하지 않았습니다. 강력한 사용 사례가 제시되지 않았습니다. 대부분의 주류 언어는 지역 사회의 불만없이 그것을 허용하지 않습니다. lang(및 impl)은 레이블이 지정된 직접 문이 루프/스위치에 레이블을 지정하는 것이 더 간단하고 명확합니다.

디자인 회의

TBD