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

사랑에 대한 내 개인적인 주요 이유 중 하나입니다 위해 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있습니까?



답변

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

을 통해 떨어지는 switchcase의 것은 아무런 코드가없는함으로써 달성 될 수있다 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;
}


답변

“이유”는 실수로 인한 실수를 피하는 것입니다. 감사합니다. 이것은 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에 너무 가깝습니다-범위 지정 등으로 변경 될 수있는 유용한 변경 사항이 있습니다. 패턴 매칭 등을 수행 할 수있는 더 똑똑한 스위치가 도움이 될 것입니다. -이 시점에서 다른 이름이 필요할 수 있습니다.


답변

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

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


답변

여기에 답변을 추가하려면 이와 관련하여 반대의 질문을 고려해 볼 가치가 있다고 생각합니다. 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에 적합합니다 .

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


답변

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

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


답변

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

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

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


답변

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