C #에서 스위치 문이 실패합니까?


365

사랑에 대한 내 개인적인 주요 이유 중 하나입니다 위해 fallthrough switch 문 switch대의 if/else if구조를. 예를 들면 다음과 같습니다.

static string NumberToWords(int number)
{
    string[] numbers = new string[] 
        { "", "one", "two", "three", "four", "five", 
          "six", "seven", "eight", "nine" };
    string[] tens = new string[] 
        { "", "", "twenty", "thirty", "forty", "fifty", 
          "sixty", "seventy", "eighty", "ninety" };
    string[] teens = new string[]
        { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
          "sixteen", "seventeen", "eighteen", "nineteen" };

    string ans = "";
    switch (number.ToString().Length)
    {
        case 3:
            ans += string.Format("{0} hundred and ", numbers[number / 100]);
        case 2:
            int t = (number / 10) % 10;
            if (t == 1)
            {
                ans += teens[number % 10];
                break;
            }
            else if (t > 1)
                ans += string.Format("{0}-", tens[t]);
        case 1:
            int o = number % 10;
            ans += numbers[o];

            break;
        default:
            throw new ArgumentException("number");
    }
    return ans;
}

string[]s는 함수 외부에서 선언되어야 하기 때문에 똑똑한 사람들이 울고 있습니다.

컴파일러는 다음 오류와 함께 실패합니다.

한 케이스 레이블 ( 'case 3 :')에서 다른 케이스 레이블로 제어를 넘어갈 수 없습니다.
한 케이스 레이블 ( 'case 2 :')에서 다른 케이스 레이블로 제어를 넘어갈 수 없습니다.

왜? 그리고 세 가지없이 이러한 종류의 행동을 취할 수있는 방법이 if있습니까?

답변:


648

(복사 / 붙여 넣기 다른 곳에서 제공 답변 )

을 통해 떨어지는 switch- case의 것은 아무런 코드가없는함으로써 달성 될 수있다 case(참조 case 0), 또는 특수를 사용하여 goto case(참조 case 1) 또는 goto default(참조 case 2양식) :

switch (/*...*/) {
    case 0: // shares the exact same code as case 1
    case 1:
        // do something
        goto case 2;
    case 2:
        // do something else
        goto default;
    default:
        // do something entirely different
        break;
}

121
이 특별한 경우에 goto는 유해한 것으로 간주되지 않습니다.
Thomas Owens

78
젠장-1.0 초기부터 C #으로 프로그래밍 해 왔으며 지금까지 본 적이 없습니다 . 보여 주기만하면 매일 새로운 것을 배웁니다.
Erik Forbes

37
다 괜찮아, 에릭 / I /가 알고있는 유일한 이유는 돋보기를 사용하여 ECMA-334 사양을 읽는 컴파일러 이론이 대단하다는 것입니다.
Alex Lyman

13
@ Dancrumb : 기능이 작성된 시점에서 C #은 "soft"키워드 ( 'yield', 'var', 'from'및 'select'와 같은) "soft"키워드를 아직 추가하지 않았으므로 세 가지 실제 옵션이 있습니다. 1 ) '폴 스루'를 하드 키워드로 만들 수 있습니다 (변수 이름으로 사용할 수 없음), 2) 소프트 키워드를 지원하는 데 필요한 코드 작성, 3) 이미 예약 된 키워드 사용. # 1은 포팅 코드에서 큰 문제였습니다. # 2는 내가 이해 한 것으로부터 상당히 큰 엔지니어링 작업이었습니다. # 3와 함께 할 수있는 옵션은 부수적 인 이점이있었습니다. 사실 다른 개념의 개발자들은 코드의 기본 개념 인 goto의 기본 개념에서 기능에 대해 배울 수 있습니다.
Alex Lyman

10
이 모든 것은 명백한 실패를위한 새로운 / 특별 키워드에 대해 이야기합니다. 그들은 단지 'continue'키워드를 사용하지 못했습니까? 다시 말해. 스위치를 끊거나 다음 경우로 넘어갑니다 (내림).
탈 Even-Tov

44

"이유"는 실수로 인한 실수를 피하는 것입니다. 감사합니다. 이것은 C 및 Java의 드문 버그 소스가 아닙니다.

해결 방법은 goto를 사용하는 것입니다.

switch (number.ToString().Length)
{
    case 3:
        ans += string.Format("{0} hundred and ", numbers[number / 100]);
        goto case 2;
    case 2:
    // Etc
}

스위치 / 케이스의 일반적인 디자인은 내 생각에 약간 불행합니다. 그것은 C에 너무 가깝습니다-범위 지정 등으로 변경 될 수있는 유용한 변경 사항이 있습니다. 패턴 매칭 등을 수행 할 수있는 더 똑똑한 스위치가 도움이 될 것입니다. -이 시점에서 다른 이름이 필요할 수 있습니다.


1
이것이 스위치와 if / elseif의 차이점입니다. 스위치는 단일 변수의 다양한 상태를 확인하는 데 사용되는 반면 if / elseif를 사용하여 연결된 여러 항목을 확인할 수 있지만 반드시 단일 변수 또는 동일한 변수는 아닙니다.
Matthew Scharley

2
실수로 넘어지지 않도록 컴파일러 경고가 더 좋을 것이라고 생각합니다. if 문에 과제가있는 경우와 마찬가지로if (result = true) { }
Tal Even-Tov

3
@ TalEven-Tov : 컴파일러 경고는 코드를 거의 항상 더 잘 수정하도록 할 수있는 경우를위한 것입니다. 개인적으로 나는 암시적인 깨기를 선호하기 때문에 처음에는 문제가되지 않지만 다른 문제입니다.
Jon Skeet

26

스위치 폴 스루는 역사적으로 최신 소프트웨어의 주요 버그 소스 중 하나입니다. 처리하지 않고 다음 사례를 직접 기본값으로 지정하지 않는 한 언어 디자이너는 사례가 끝날 때 점프해야한다고 결정했습니다.

switch(value)
{
    case 1:// this is still legal
    case 2:
}

23
왜 "사례 1, 2 :"가 아닌지 잘 모르겠습니다.
BCS

11
@David Pfeffer : 그렇습니다. 그렇게 case 1, 2:하는 언어도 있습니다. 내가 결코 이해하지 못하는 것은 현대 언어가 그것을 허용하지 않는 이유입니다.
BCS

goto 문으로 @BCS를 사용하면 쉼표로 구분 된 여러 옵션을 다루기가 까다로울 수 있습니까?
penguat

@ pengut : 말하는 것이 더 정확할 수 있습니다. case 1, 2: 단일 레이블이지만 여러 이름이있는 . -FWIW, 나는 넘어가는 것을 금지하는 대부분의 언어가 "연속적인 케이스 레이블"인듐이 아니라 케이스 레이블을 다음 명령문의 주석으로 취급하고 (1 또는 더) 케이스 레이블은 점프합니다.
BCS

22

여기에 답변을 추가하려면 이와 관련하여 반대의 질문을 고려해 볼 가치가 있다고 생각합니다. C는 왜 처음부터 실패를 허용 했습니까?

물론 모든 프로그래밍 언어는 두 가지 목표를 제공합니다.

  1. 컴퓨터에 지침을 제공하십시오.
  2. 프로그래머의 의도를 기록하십시오.

따라서 모든 프로그래밍 언어의 작성은이 두 가지 목표를 가장 잘 수행하는 방법 사이의 균형입니다. 한편으로 컴퓨터 명령어, IL과 같은 바이트 코드 또는 명령어가 실행시 해석되는지 여부에 관계없이 컴퓨터 명령어로 전환하는 것이 더 쉬워지며 컴파일 또는 해석 프로세스가 효율적이고 신뢰할 수 있으며 컴팩트 한 출력. 가장 쉬운 컴파일은 컴파일이 전혀없는 곳에 있기 때문에이 목표는 어셈블리, IL 또는 원시 op 코드로 작성하는 것입니다.

반대로, 언어가 프로그래머의 의도를 표현하기 위해 사용하는 수단보다는 프로그래밍의 의도가 많을수록 프로그램 작성 및 유지 관리 중에 프로그램을 더 잘 이해할 수 있습니다.

지금, switch 항상 if-else블록 체인 또는 이와 유사한 블록 으로 변환 하여 컴파일 할 수 있었지만 값을 가져 와서 오프셋을 계산하는 특정 공통 어셈블리 패턴으로 컴파일 할 수 있도록 설계되었습니다 (테이블을 조회하든 상관없이) 값의 완벽한 해시 또는 값에 대한 실제 산술로 색인화 됨). 이 시점에서 오늘날 C # 컴파일은 때때로 switch동등한 것으로 바뀌고 if-else때로는 해시 기반 점프 접근 방식을 사용합니다 (C, C ++ 및 비슷한 구문을 가진 다른 언어와 마찬가지로).

이 경우 대체를 허용해야하는 두 가지 이유가 있습니다.

  1. 어쨌든 자연스럽게 발생합니다. 점프 테이블을 일련의 명령으로 빌드하고 이전 명령 모음 중 하나에 어떤 종류의 점프 또는 반환이 포함되어 있지 않으면 실행이 자연스럽게 다음 배치로 진행됩니다. 넘어가는 것을 허용하면switchC를 사용하여 점프 테이블을 사용하는 기계어 코드로 .

  2. 어셈블리에서 작성한 코더는 이미 이에 상응하는 방식으로 사용되었습니다. 어셈블리에서 직접 점프 테이블을 작성할 때 주어진 코드 블록이 리턴으로 끝나거나 테이블 외부로 점프하는지 아니면 계속 진행해야하는지 고려해야합니다. 다음 블록으로. 따라서, break필요할 때 코더가 명시 적으로 추가하도록하는 것은 코더 에게 "자연적"이었습니다.

따라서 당시에는 생산 된 기계어 코드와 소스 코드의 표현 성과 관련하여 컴퓨터 언어의 두 가지 목표의 균형을 맞추는 것이 합당한 시도였습니다.

그러나 40 년 후 몇 가지 이유로 상황이 완전히 같지 않습니다.

  1. 오늘날 C의 코더는 조립 경험이 거의 없거나 전혀 없을 수 있습니다. 다른 많은 C 스타일 언어의 코더는 (특히 Javascript!) 가능성이 훨씬 낮습니다. "사람들이 어셈블리에서 익숙한"개념은 더 이상 관련이 없습니다.
  2. 최적화 개선은 다음 switch중 하나로 전환 될 가능성을 의미합니다.if-else 은 접근 방식이 가장 효율적인 것으로 간주 가능성이 높거나 점프 테이블 방식의 특히 난해한 변형으로 바뀔 가능성이 높다는 것을 의미합니다. 상위 수준과 하위 수준의 접근 방식 간의 매핑은 예전만큼 강력하지 않습니다.
  3. 경험에 따르면 폴 스루 (fall-through)가 표준이 아닌 소수 사례 인 것으로 나타났습니다 (Sun의 컴파일러에 대한 연구에 따르면 switch블록의 3 %가 동일한 블록에 여러 레이블이 아닌 다른 레이블을 사용한 것으로 나타났습니다. 여기서는이 3 %가 실제로 정상보다 훨씬 높음을 의미합니다. 따라서 연구에 사용 된 언어는 일반적인 것보다 특이한 것을 더 쉽게 제공합니다.
  4. 경험에 따르면 실수는 실수로 수행 된 경우와 코드를 유지 관리하는 사람이 올바른 실패를 놓치는 경우 모두 문제의 원인이되는 것으로 나타났습니다. 후자는 폴 스루와 관련된 버그에 미묘한 추가 사항입니다. 코드에 버그가없는 경우에도 폴 스루는 여전히 문제를 일으킬 수 있기 때문입니다.

마지막 두 가지 요점과 관련하여 현재 K & R 에디션의 다음 인용문을 고려하십시오.

한 사례에서 다른 사례로 넘어가는 것은 강력하지 않으며 프로그램이 수정 될 때 붕괴되기 쉽다. 단일 계산에 대해 여러 레이블을 제외하고, 추월은 드물게 사용하고 주석을 달아야합니다.

좋은 형태의 문제로 논리적으로 불필요하더라도 마지막 경우 (여기서는 기본값) 뒤에 휴식을 취하십시오. 언젠가 다른 사건이 추가되면 언젠가는 방어적인 프로그래밍이 당신을 구할 것입니다.

따라서, 말의 입에서 C로 넘어지는 것은 문제가됩니다. 주석을 사용하여 오류를 항상 문서화하는 것이 좋습니다. 이는 일반적으로 예외가 발생하는 위치를 문서화해야한다는 일반적인 원칙의 적용입니다. 이는 나중에 코드를 검토하거나 코드를 모양과 다르게 보이게하기 때문입니다. 실제로 올바른 경우 초보자의 버그가 있습니다.

그리고 당신이 그것에 대해 생각할 때, 다음과 같은 코드 :

switch(x)
{
  case 1:
   foo();
   /* FALLTHRU */
  case 2:
    bar();
    break;
}

입니다 뭔가를 추가하는 가을 -을 통해 코드에서 명시 적으로 만들기 위해, 그냥 컴파일러에 의해 (그의 부재 검출 할 수 또는)을 검출 할 수있는 일이 아니다.

따라서 C #에서 폴 스루로 on을 명시해야한다는 사실은 다른 C 스타일 언어로 잘 쓴 사람들에게 어쨌든 벌칙을 추가하지 않습니다. 그들은 이미 폴 스루에서 명백하기 때문입니다. †

마지막으로 goto여기 에서 사용하는 것은 이미 C와 다른 언어의 표준입니다.

switch(x)
{
  case 0:
  case 1:
  case 2:
    foo();
    goto below_six;
  case 3:
    bar();
    goto below_six;
  case 4:
    baz();
    /* FALLTHRU */
  case 5:
  below_six:
    qux();
    break;
  default:
    quux();
}

이런 종류의 블록에서 블록을 이전 블록으로 가져 오는 값 이외의 값으로 실행하려면 코드를 이미 사용해야 goto합니다. (물론, 다른 조건으로 이것을 피할 수있는 수단과 방법이 있지만 이것은이 질문과 관련된 모든 것에 해당됩니다). 이러한 C #은 이미 일반적인 방법을 기반으로하여 하나의 코드 블록을 두 개 이상의 코드 블록에 switch맞추고 넘어지기를 다루기 위해 일반화했습니다. 또한 C에 새 레이블을 추가해야하지만 caseC #에서 레이블로 사용할 수 있으므로 두 경우 모두 더 편리하고 자체 문서화 되었습니다. C #에서는 below_six레이블을 제거 goto case 5하고 우리가하는 일에 대해 더 명확한 것을 사용할 수 있습니다. (우리는 또한 추가해야break 하고default위의 C 코드를 명확하게 C # 코드로 만들지 않기 위해 생략했습니다.)

그러므로 요약하면 :

  1. C #은 40 년 전 C 코드처럼 직접 최적화되지 않은 컴파일러 출력과 더 이상 관련이 없습니다 (요즘 C도 아님). 이는 추락의 영감 중 하나를 무의미하게 만듭니다.
  2. C #은 break유사한 언어에 익숙한 사용자가 쉽게 언어를 배우고 쉽게 포팅 할 수 있도록 암시 적 기능 을 제공하는 것이 아니라 C와 호환됩니다 .
  3. C #은 지난 40 년 동안 문제를 일으킨 것으로 문서화 된 버그 나 오해의 원인을 제거합니다.
  4. C #을 사용하면 컴파일러에서 C (문서 대체)를 적용하여 기존 모범 사례를 만들 수 있습니다.
  5. C #은 특이한 경우를보다 명확한 코드로 작성하고, 일반적인 경우는 코드를 작성하여 자동으로 작성합니다.
  6. C #은 C에서 사용되는 것과 같은 goto다른 case레이블 에서 동일한 블록을 치는 데 동일한 기반의 접근 방식을 사용합니다 . 다른 경우에만 일반화합니다.
  7. C #을 사용하면 문에 레이블로 작용할 goto수 있으므로 이러한 접근 방식이 C 보다 편리하고 명확 해 case집니다.

대체로 합리적인 디자인 결정


* 일부 형태의 베이직은 취향에 GOTO (x AND 7) * 50 + 240따라 취성 을 허용 하는 반면, 특히 금지 goto에 대한 설득력있는 사례 는 하위 수준 코드가 값에 대한 산술. 수동으로 유지 관리해야하는 것이 아니라 컴파일 결과 일 때 훨씬 더 합리적입니다. Duff 's Device의 구현은 특히 nop필러를 추가 할 필요없이 각 명령어 블록의 길이가 동일하기 때문에 동등한 기계 코드 또는 IL에 적합합니다 .

† 더프 장치는 합리적인 예외로 여기에 다시 나타납니다. 그와 유사한 패턴으로 작업이 반복된다는 사실은 그 영향에 대한 명시 적 언급 없이도 대체를 명확하게 사용하는 역할을합니다.


17

'고토 케이스 라벨' http://www.blackwasp.co.uk/CSharpGoto.aspx

goto 문은 프로그램 제어를 다른 명령문으로 무조건 전송하는 간단한 명령입니다. 이 명령은 스파게티 코드로 이어질 수 있기 때문에 모든 고급 프로그래밍 언어에서의 제거를 옹호하는 일부 개발자에게 종종 비난을 받습니다. 이것은 goto 문이나 유사한 jump 문이 너무 많아서 코드를 읽고 유지하기가 어려울 때 발생합니다. 그러나 goto 문은 신중하게 사용하면 일부 문제에 대한 우아한 해결책을 제공한다고 지적하는 프로그래머가 있습니다 ...


8

그들은 의도적으로 사용되지 않았지만 문제를 일으킨 때를 피하기 위해 의도적으로이 동작을 생략했습니다.

다음과 같이 케이스 부분에 명령문이없는 경우에만 사용할 수 있습니다.

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}

4

그들은 C #에 대한 switch 문 (C / Java / C ++에서) 동작을 변경했습니다. 사람들이 추락에 대해 잊어 버렸고 오류가 발생했다고 추론합니다. 내가 읽은 한 책은 goto를 사용하여 시뮬레이션한다고 말했지만 이것은 나에게 좋은 해결책처럼 들리지 않습니다.


2
C #은 goto를 지원하지만 실패하지는 않습니까? 와. 그리고 그것은 단지 그런 것이 아닙니다. C #은 내가 아는 유일한 언어입니다.
Matthew Scharley

처음에는 정확히 마음에 들지 않았지만 "fall-thru"는 실제로 재난을위한 레시피입니다 (특히 주니어 프로그래머들 사이). 많은 사람들이 지적했듯이 C #은 여전히 ​​빈 줄에 대해 fall-thru를 허용합니다. 사례.) "Kenny"는 스위치 케이스와 함께 우아한 Goto 사용을 강조하는 링크를 게시했습니다.
Pretzel

내 의견으로는 별거 아니야. 99 %의 시간이 넘어지고 싶지 않고 과거의 벌레에 의해 화상을 입었습니다.
Ken

1
"이것은 나에게 좋은 해결책 같지 않다"-당신에 대해 듣게되어 유감 goto case입니다. 그것이 바로 그 목적 이기 때문입니다 . 폴 스루에 대한 장점은 명시 적이라는 것입니다. 여기에있는 일부 사람들 goto case은 문제에 대한 이해없이 "고토"에 대해 교리되어 있고 스스로 생각할 수 없다는 것을 보여주기 위해 반대합니다. Dijkstra는 "GOTO로 인해 해로운 것으로 간주"를 썼을 때, 제어 흐름을 바꿀 다른 수단이없는 언어를 다루고있었습니다.
Jim Balter

@JimBalter 그리고 Dijkstra를 인용하는 사람들은 Knuth가 goto코드를 최적화 할 때 얼마나 유용 할 수 있는지에 대해 명시 적으로 글을 썼을 때 "누르기 최적화는 모든 악의 근원"이라고 Knuth를 인용 할 것입니까?
Jon Hanna

1

goto 키워드를 사용하면 C ++처럼 넘어 질 수 있습니다.

전의:

switch(num)
{
   case 1:
      goto case 3;
   case 2:
      goto case 3;
   case 3:
      //do something
      break;
   case 4:
      //do something else
      break;
   case default:
      break;
}

9
2 년 전에 누군가 만 게시했다면!

0

case 문인지 default 문인지 여부에 관계없이 마지막 블록을 포함하여 각 case 블록 뒤에 break와 같은 jump 문이 필요합니다. C ++ 스위치 문과 달리 한 가지 예외를 제외하고 C #은 한 사례 레이블에서 다른 사례 레이블로의 암시 적 하락을 지원하지 않습니다. 한 가지 예외는 case 문에 코드가없는 경우입니다.

- C #을 스위치 () 문서


1
나는이 행동이 문서화되어 있음을 알고 있으며 그것이 왜 그런지 알고 싶고 오래된 행동을 취하는 대안을 알고 싶습니다.
Matthew Scharley

0

각 경우마다 기본 사례 인 경우에도 break 또는 goto 문이 필요합니다.


2
2 년 전에 누군가 만 게시했다면!

1
@Poldie 처음으로 재밌었습니다 ... 실파는 모든 경우에 대한 휴식이나 이동이 필요하지 않으며 모든 경우에 대해 자체 코드가 있습니다. 코드를 잘 공유하는 여러 사례를 가질 수 있습니다.
Maverick

0

Xamarin의 컴파일러가 실제로이 문제를 해결했으며 실패를 허용한다는 간단한 참고 사항입니다. 아마도 수정되었지만 릴리스되지 않았습니다. 실제로 넘어간 일부 코드에서 이것을 발견했으며 컴파일러는 불평하지 않았습니다.


0

스위치 (C # 참조)

C #에는 마지막 섹션을 포함하여 스위치 섹션의 끝이 필요합니다.

당신은 또한을 추가 할 필요가 그래서 break;당신에 default섹션, 그렇지 않으면 여전히 컴파일러 오류가있을 것입니다 것입니다.


Thx, 이것이 도움이되었습니다.)
CareTaker22

-1

C #은 switch / case 문으로 대체를 지원하지 않습니다. 이유는 확실하지 않지만 실제로는 지원되지 않습니다. 결합


이것은 너무 잘못되었습니다. 다른 답변을 참조하십시오 ... 암시 적 넘어짐 의 효과 는 명시 적으로 얻을 수 있습니다 goto case. 이것은 C # 디자이너가 의도적이고 현명한 디자인 선택이었습니다.
Jim Balter

-11

"break"를 추가하는 것을 잊었습니다. 사례 2에 진술하십시오. 사례 2에 대해서는 if 블록에 썼습니다. 따라서 이것을 시도하십시오 :

case 3:            
{
    ans += string.Format("{0} hundred and ", numbers[number / 100]);
    break;
}


case 2:            
{
    int t = (number / 10) % 10;            
    if (t == 1)            
    {                
        ans += teens[number % 10];                
    }            
    else if (t > 1)                
    {
        ans += string.Format("{0}-", tens[t]);        
    }
    break;
}

case 1:            
{
    int o = number % 10;            
    ans += numbers[o];            
    break;        
}

default:            
{
    throw new ArgumentException("number");
}

2
이것은 매우 잘못된 출력을 생성합니다. 스위치 선언문은 의도적으로 생략했습니다. 문제는 C # 컴파일러가 다른 언어 에이 제한이 거의 없을 때 이것을 오류로 보는 이유입니다.
Matthew Scharley

5
이해하기에 참으로 놀라운 실패입니다. 그리고 당신은 이것을 삭제하는데 5 년이 걸렸지 만 여전히 그렇게하지 않았습니까? 마인드 글링.
Jim Balter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.