C # 스위치 명령문 제한-왜? case typeof(string):

switch 문을 작성할 때 case 문에서 켤 수있는 항목에는 두 가지 제한이있는 것으로 보입니다.

예를 들어 (그렇습니다, 당신이 이런 종류의 일을하고 있다면 그것은 아마도 객체 지향 (OO) 아키텍처가 iffy라는 것을 의미합니다 -이것은 단지 고안된 예입니다!)

  Type t = typeof(int);

  switch (t) {

    case typeof(int):
      Console.WriteLine("int!");
      break;

    case typeof(string):
      Console.WriteLine("string!");
      break;

    default:
      Console.WriteLine("unknown!");
      break;
  }

여기서 switch () 문은 ‘정수 유형의 예상 값’으로 실패하고 사례 문은 ‘상수 값이 예상됩니다’로 실패합니다.

이러한 제한이 필요한 이유는 무엇이며 기본 정당성은 무엇입니까? switch 문 정적 분석에만 성공해야하는 이유와 켜져있는 값이 완전한 (즉, 기본) 이유를 알 수 없습니다. 정당화 란 무엇입니까?



답변

이것은 … 어떤 토론을 촉발 내 원래의 게시물입니다 잘못 때문에 :

switch 문은 큰 if-else 문과 동일하지 않습니다. 각 사례는 고유해야하며 정적으로 평가되어야합니다. switch 문은 보유한 사례 수에 관계없이 일정한 시간 분기를 수행합니다. if-else 문은 각 조건이 참일 때까지 각 조건을 평가합니다.


실제로 C # switch 문은 항상 일정한 시간 분기 가 아닙니다 .

어떤 경우에는 컴파일러가 CIL switch 문을 사용하는데, 이는 실제로 점프 테이블을 사용하여 일정한 시간 분기입니다. 그러나 Ivan Hamilton 이 지적한 드문 경우 에는 컴파일러가 완전히 다른 것을 생성 할 수 있습니다.

이것은 다양한 C # 스위치 문, 일부 희박하고 밀도가 높고 ildasm.exe 도구를 사용하여 결과 CIL을 보면 실제로 쉽게 확인할 수 있습니다.


답변

C # switch 문과 CIL switch 명령을 혼동하지 않는 것이 중요합니다.

CIL 스위치는 점프 테이블이므로 점프 주소 세트에 대한 색인이 필요합니다.

이것은 C # 스위치의 케이스가 인접한 경우에만 유용합니다.

case 3: blah; break;
case 4: blah; break;
case 5: blah; break;

그러나 그렇지 않은 경우 거의 사용하지 않습니다.

case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;

(3 개의 슬롯 만 사용하여 ~ 3000 개의 테이블 크기 항목이 필요합니다)

인접하지 않은 표현식을 사용하면 컴파일러는 선형 if-else-if-else 검사를 수행하기 시작할 수 있습니다.

인접하지 않은 더 큰 표현식 세트를 사용하면 컴파일러는 이진 트리 검색으로 시작하여 마지막으로 몇 개의 항목 만 if-else-if-else로 시작할 수 있습니다.

인접한 항목의 덩어리를 포함하는 표현식 세트를 사용하면 컴파일러는 이진 트리 검색과 마지막으로 CIL 스위치를 사용할 수 있습니다.

이것은 “mays”& “mights”로 가득 차 있으며 컴파일러에 따라 다릅니다 (Mono 또는 Rotor와 다를 수 있음).

인접한 경우를 사용하여 결과를 컴퓨터에 복제했습니다.

10 웨이 스위치를 실행하는 총 시간, 10000 회 반복 (ms) : 25.1383
10 웨이 스위치 당 대략적인 시간 (ms) : 0.00251383

50 웨이 스위치를 실행하는 총 시간, 10000 회 반복 (ms) : 26.593
50 웨이 스위치 당 대략적인 시간 (ms) : 0.0026593

5000 웨이 스위치를 실행하는 총 시간, 10000 반복 (ms) :
5000 웨이 스위치 (ms) 당 대략적인 시간 : 23.7094 : 0.00237094

50000 웨이 스위치를 실행하는 총 시간, 10000 반복 (ms) : 205000 년
스위치 (ms) 당 대략적인 시간 : 0.00200933

그런 다음 인접하지 않은 케이스 표현식을 사용했습니다.

10 웨이 스위치를 실행하는 총 시간, 10000 회 반복 (ms) : 19.6189
10 웨이 스위치 당 대략적인 시간 (ms) : 0.00196189

500 웨이 스위치를 실행하는 총 시간, 10000 반복 (ms) : 19.1664
500 웨이 스위치 (ms) 당 대략적인 시간 : 0.00191664

5000 웨이 스위치를 실행하는 총 시간, 10000 반복 (ms) :
5000 웨이 스위치 (ms) 당 대략적인 시간 : 19.5871 : 0.00195871

인접하지 않은 50,000 개의 case switch 문은 컴파일되지 않습니다.
“표현식이 너무 길거나 복잡하여 ‘ConsoleApplication1.Program.Main (string [])’근처에서 컴파일 할 수 없습니다.

여기서 재미있는 점은 이진 트리 검색이 CIL 스위치 명령보다 약간 (통계적으로는 그렇지 않음) 빠르다는 것입니다.

Brian, ” constant ” 라는 단어를 사용했는데 , 이는 계산 복잡도 이론 관점에서 매우 명확한 의미를 갖습니다. 단순한 인접 정수 예제는 O (1) (일정)로 간주되는 CIL을 생성 할 수 있지만, 드문 예제는 O (log n) (대수)이며 군집 된 예제는 중간에 있고 작은 예제는 O (n) (선형)입니다. ).

이것은 정적을 Generic.Dictionary<string,int32>만들 수있는 String 상황을 다루지 않으며 처음 사용할 때 명확한 오버 헤드를 겪게됩니다. 여기서의 성능은의 성능에 따라 다릅니다 Generic.Dictionary.

CIL 사양이 아닌 C # 언어 사양 을 확인하면 “15.7.2 switch statement”에 “일정한 시간”에 대한 언급이 없거나 기본 구현에서 CIL 스위치 명령어를 사용하기도합니다 (매우 신중하게 가정해야 함). 그런 것들).

하루가 끝나면 현대 시스템의 정수 식에 대한 C # 스위치는 마이크로 초 미만의 작업이며 일반적으로 걱정할 필요가 없습니다.


물론이 시간은 기계와 조건에 따라 다릅니다. 이 타이밍 테스트에주의를 기울이지 않을 것입니다. 우리가 말하는 마이크로 초의 지속 시간은 실행중인 “실제”코드로 인해 난장이입니다 (그리고 “실제 코드”를 포함해야합니다. 그렇지 않으면 컴파일러가 분기를 최적화합니다). 시스템의 지터. 내 대답은 IL DASM 을 사용 하여 C # 컴파일러로 만든 CIL을 검사 한 결과입니다. 물론 CPU가 실행하는 실제 명령어는 JIT에 의해 생성되므로 최종적인 것은 아닙니다.

x86 컴퓨터에서 실제로 실행되는 최종 CPU 명령어를 확인했으며 다음과 같은 간단한 인접 세트 스위치를 확인할 수 있습니다.

  jmp     ds:300025F0[eax*4]

이진 트리 검색이 가득한 경우 :

  cmp     ebx, 79Eh
  jg      3000352B
  cmp     ebx, 654h
  jg      300032BB
  
  cmp     ebx, 0F82h
  jz      30005EEE


답변

가장 먼저 떠오르는 이유는 역사적입니다 .

대부분의 C, C ++ 및 Java 프로그래머는 이러한 자유를 얻는 데 익숙하지 않기 때문에 요구하지 않습니다.

또 다른 더 유효한 이유는 언어 복잡성이 증가 한다는 것입니다 .

우선, 객체 .Equals()==연산자를 비교해야 합니까? 두 경우 모두 유효합니다. 이를 위해 새로운 구문을 도입해야합니까? 프로그래머가 자체 비교 방법을 소개하도록 허용해야합니까?

또한 객체를 켤 수있게 하면 switch 문에 대한 기본 가정 집니다. 개체를 켤 수있는 경우 컴파일러에서 적용 할 수없는 switch 문에 적용되는 두 가지 규칙이 있습니다 ( C # 버전 3.0 언어 사양 , §8.7.2 참조).

  • 스위치 라벨의 값이 일정하다는 것을
  • 스위치 레이블의 값이 고유해야합니다 (따라서 주어진 스위치 표현에 대해 하나의 스위치 블록 만 선택할 수 있습니다)

상수가 아닌 경우 값이 허용되었다는 가설의 경우이 코드 예제를 고려하십시오.

void DoIt()
{
    String foo = "bar";
    Switch(foo, foo);
}

void Switch(String val1, String val2)
{
    switch ("bar")
    {
        // The compiler will not know that val1 and val2 are not distinct
        case val1:
            // Is this case block selected?
            break;
        case val2:
            // Or this one?
            break;
        case "bar":
            // Or perhaps this one?
            break;
    }
}

코드는 무엇을합니까? 사례 진술이 재정렬되면 어떻게됩니까? 실제로, C #이 스위치를 잘못 통과 한 이유 중 하나는 스위치 문을 임의로 재 배열 할 수 있기 때문입니다.

이러한 규칙은 이유가 있으므로 프로그래머는 하나의 사례 블록을보고 블록이 입력되는 정확한 조건을 알 수 있습니다. 위에서 언급 한 스위치 문이 100 줄 이상으로 커지면 그러한 지식은 매우 중요합니다.


답변

그런데 동일한 기본 아키텍처를 가진 VB는 훨씬 더 유연한 Select Case명령문을 허용 하고 (위 코드는 VB에서 작동 할 수 있음) 이것이 가능한 경우 효율적인 코드를 생성하므로 기술적 제약 조건에 의한 인수를 신중하게 고려해야합니다.


답변

대부분 언어 디자이너 때문에 이러한 제한 사항이 적용됩니다. 근본적인 정당성은 언어 이력, 이상 또는 컴파일러 설계의 단순화와의 호환성 일 수 있습니다.

컴파일러는 다음을 수행 할 수 있습니다.

  • 큰 if-else 문을 작성하십시오
  • MSIL 스위치 명령어 사용 (점프 테이블)
  • Generic.Dictionary <string, int32>를 빌드하고 처음 사용할 때 채우고 인덱스가 MSIL 스위치 명령 (점프 테이블)에 전달되도록 Generic.Dictionary <> :: TryGetValue ()를 호출하십시오.
  • if-elses 및 MSIL “스위치”점프의 조합을 사용하십시오.

switch 문은 일정한 시간 분기가 아닙니다. 컴파일러는 단축키 (해시 버킷 사용 등)를 찾을 수 있지만 더 복잡한 경우 더 복잡한 MSIL 코드를 생성 할 수 있습니다.

String 경우를 처리하기 위해 컴파일러는 a.Equals (b) (및 가능하면 a.GetHashCode ())를 사용하여 (어느 시점에서) 끝납니다. 컴파일러가 이러한 제약 조건을 만족시키는 객체를 사용하는 것은 사소한 일이라고 생각합니다.

정적 케이스 표현식의 필요성에 관해서는 … 케이스 표현식이 결정적이지 않은 경우 최적화 (해싱, 캐싱 등) 중 일부를 사용할 수 없습니다. 그러나 우리는 이미 컴파일러가 어쨌든 단순한 if-else-if-else 도로를 선택하는 것을 보았습니다 …

편집 : lomaxx- “typeof”연산자에 대한 이해가 올바르지 않습니다. “typeof”연산자는 형식에 대한 System.Type 개체를 얻는 데 사용됩니다 (상위 형식이나 인터페이스와는 관련이 없음). 주어진 유형을 가진 객체의 런타임 호환성을 확인하는 것은 “is”운영자의 작업입니다. 여기서 “typeof”를 사용하여 객체를 표현하는 것은 관련이 없습니다.


답변

Jeff Atwood에 따르면이 주제에 대해 switch 문은 프로그래밍 문제 입니다. 드물게 사용하십시오.

종종 테이블을 사용하여 동일한 작업을 수행 할 수 있습니다. 예를 들면 다음과 같습니다.

var table = new Dictionary<Type, string>()
{
   { typeof(int), "it's an int!" }
   { typeof(string), "it's a string!" }
};

Type someType = typeof(int);
Console.WriteLine(table[someType]);


답변

switch 문이 정적 분석에만 의존 해야하는 이유는 없습니다.

사실, 그것은하지 않습니다 에, 많은 언어는 실제로 사용하는 동적 스위치 문에서 않습니다. 그러나 “case”절의 순서를 바꾸면 코드의 동작이 변경 될 수 있습니다.

여기에 “스위치”로 들어가는 디자인 결정 뒤에는 흥미로운 정보가 있습니다. 왜 C # 스위치 문이 폴 스루를 허용하지 않지만 여전히 중단이 필요하도록 설계 되었습니까?

동적 케이스 표현식을 허용하면 다음 PHP 코드와 같은 괴물이 생길 수 있습니다.

switch (true) {
    case a == 5:
        ...
        break;
    case b == 10:
        ...
        break;
}

솔직히 그 if-else진술을 사용해야합니다 .