C #에 케이스 블록에 로컬 범위가없는 이유는 무엇입니까?


38

나는이 코드를 작성했다 :

private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
    switch (criterion.ChangeAction)
    {
        case BindingType.Inherited:
            var action = (byte)ChangeAction.Inherit;
            return (x => x.Action == action);
        case BindingType.ExplicitValue:
            var action = (byte)ChangeAction.SetValue;
            return (x => x.Action == action);
        default:
            // TODO: Localize errors
            throw new InvalidOperationException("Invalid criterion.");
    }
}

그리고 컴파일 오류를 발견 한 것에 놀랐습니다.

'action'이라는 지역 변수가 이미이 범위에 정의되어 있습니다.

해결하기 매우 쉬운 문제였습니다. 두 번째를 제거하는 var것이 트릭을 수행했습니다.

분명히 case블록에 선언 된 변수는 parent의 범위를 switch갖지만 이것이 왜 그런지 궁금합니다. C #은 실행이 (가 다른 경우를 통해 떨어지지 않는 것을 감안할 필요 break , return, throw, 또는 goto case모든의 끝에 문 case블록), 그것은 하나의 내부는 변수 선언을 허용 것이 매우 이상한 것 같다 case다른 변수와 함께 사용하거나 충돌 할 case. 다시 말해 case, 실행이 불가능하더라도 변수는 명령문을 통과하는 것으로 보입니다 . C #은 혼란 스럽거나 쉽게 남용되는 다른 언어의 일부 구문을 금지하여 가독성을 높이기 위해 많은 노력을 기울입니다. 그러나 이것은 혼란을 야기 할 수밖에없는 것처럼 보입니다. 다음 시나리오를 고려하십시오.

  1. 이것을 다음과 같이 바꾸려면 :

    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    case BindingType.ExplicitValue:
        return (x => x.Action == action);

    " 할당되지 않은 로컬 변수 'action' "이 사용되었습니다. 내가 생각할 수있는 C #의 다른 모든 구문에서 var action = ...변수를 초기화 하기 때문에 혼란 스럽지만 여기서는 단순히 변수를 선언합니다.

  2. 내가 이런 경우를 바꾸려면 :

    case BindingType.ExplicitValue:
        action = (byte)ChangeAction.SetValue;
        return (x => x.Action == action);
    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);

    " 로컬 변수 'action'을 (를) 선언하기 전에 사용할 수 없습니다 "가 표시됩니다. 일반적으로 내가 어떤 주문 I 소원 이러한 쓸 수 있지만,이 때문에 - 케이스 블록의 순서는 완전히 명확하지의 방법으로 여기에 중요한 것으로 나타납니다 그래서 var첫 번째 블록에 표시되어야합니다 action사용되는, 내가 조정할 필요가 case블록을 따라서.

  3. 이것을 다음과 같이 바꾸려면 :

    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    case BindingType.ExplicitValue:
        action = (byte)ChangeAction.SetValue;
        goto case BindingType.Inherited;

    그런 다음 오류가 발생하지 않지만 어떤 의미에서는 변수가 선언되기 전에 값이 할당 된 것처럼 보입니다 .
    (실제로 이것을하고 싶을 때를 생각할 수는 없지만 goto case오늘 전에는 존재 조차 알지 못했습니다 )

그래서 제 질문은 C #의 디자이너가 왜 case자신의 로컬 범위를 블록에 제공하지 않았 습니까? 이에 대한 역사적 또는 기술적 이유가 있습니까?


4
명령문 action앞에 변수를 선언 switch하거나 각 케이스를 중괄호에 넣으면 합리적인 동작이 발생합니다.
Robert Harvey

3
@RobertHarvey 예, 더 이상 사용하지 않고 이것을 쓸 수 switch있습니다.이 디자인의 이유에 대해 궁금합니다.
pswg

c #이 모든 경우에 휴식을 취해야한다면 c / java와 같이 역사적인 이유로해야한다고 생각합니다.
tgkprog

@tgkprog 나는 역사적인 이유가 아니며 C #의 디자이너가 C #에서 a를 잊어 버리는 일반적인 실수를 방지하기 위해 의도적으로 그렇게했다고 생각합니다 break.
svick

12
이 관련 SO 질문에 대한 Eric Lippert의 답변을 참조하십시오. stackoverflow.com/a/1076642/414076 만족하지 못할 수도 있습니다. "1999 년에 선택한 방식이기 때문에"라고 읽습니다.
Anthony Pegram

답변:


24

다른 모든 경우에“정상”로컬 변수의 범위는 중괄호 ( )로 구분 된 블록 이기 때문에 좋은 이유라고 생각합니다 {}. 일반적이지 않은 지역 변수는 for루프 변수 나로 선언 된 변수 와 같이 명령문 앞에있는 특수 구문 (보통 블록)에 나타납니다 using.

또 다른 예외는 LINQ 쿼리 표현식의 로컬 변수이지만 일반적인 로컬 변수 선언과는 완전히 다르므로 혼동 될 가능성이 없다고 생각합니다.

참고로 규칙은 C # 사양의 §3.7 범위에 있습니다.

  • local-variable-declaration에 선언 된 로컬 변수의 범위는 선언 이 발생하는 블록입니다.

  • 명령문의 스위치 블록 에 선언 된 로컬 변수의 범위 switchswitch-block 입니다.

  • 명령문 의 for-initializer 에 선언 된 로컬 변수의 범위는 for-initializer , for-condition , for-iterator명령문 의 포함 된 명령문 입니다 .forfor

  • foreach-statement , using-statement , lock-statement 또는 query-expression의 일부로 선언 된 변수의 범위 는 주어진 구문의 확장에 의해 결정됩니다.

(나는 switch언급 된 다른 모든 구문과 달리 로컬 변수 감소에 대한 특별한 구문이 없으므로 블록이 명시 적으로 언급 된 이유를 완전히 확신 하지는 못합니다.)


1
+1 인용하는 범위에 대한 링크를 제공 할 수 있습니까?
pswg

@pswg MSDN 에서 사양을 다운로드하기위한 링크를 찾을 수 있습니다 . (해당 페이지의“MSDN (Microsoft Developer Network)”링크를 클릭하십시오.)
svick

그래서 Java 사양을 살펴 보았고 switches이와 관련하여 동일하게 작동하는 것 같습니다 (의 끝에 점프가 필요하다는 점은 제외 case). 이 동작은 단순히 거기에서 복사 된 것 같습니다. 그래서 짧은 대답은- case문장은 블록을 만들지 않고 단순히 블록의 나누기를 정의 switch하므로 그 자체로 범위가 없다는 것입니다.
pswg

3
Re : 스위치 블록이 왜 명시 적으로 언급되어 있는지 잘 모르겠습니다 . 사양의 저자는 까다 롭습니다. 스위치 블록 은 문법이 일반 블록과 다르다는 것을 암시 적으로 지적합니다 .
Eric Lippert

42

그러나 그렇습니다. 라인을 줄 바꿈하여 어디에서나 로컬 범위를 만들 수 있습니다{}

switch (criterion.ChangeAction)
{
  case BindingType.Inherited:
    {
      var action = (byte)ChangeAction.Inherit;
      return (x => x.Action == action);
    }
  case BindingType.ExplicitValue:
    {
      var action = (byte)ChangeAction.SetValue;
      return (x => x.Action == action);
    }
  default:
    // TODO: Localize errors
    throw new InvalidOperationException("Invalid criterion.");
}

그래이를 사용하고는 설명 작품
Mvision

1
+1 이것은 좋은 속임수이지만, 원래 질문에 가장 근접한 svick 의 답변 을 받아 들일 것 입니다.
pswg

10

에릭 리퍼 (Eric Lippert)를 인용하겠습니다.

합리적인 질문은 "왜 이것이 합법적이지 않습니까?"입니다. 합리적인 대답은 "음, 왜 그래야합니까?"입니다. 두 가지 방법 중 하나를 사용할 수 있습니다. 이것은 합법적입니다.

switch(y) 
{ 
    case 1:  int x = 123; ...  break; 
    case 2:  int x = 456; ...  break; 
}

또는 이것은 합법적입니다.

switch(y) 
{
    case 1:  int x = 123; ... break; 
    case 2:  x = 456; ... break; 
}

그러나 당신은 두 가지 방법으로 가질 수 없습니다. C #의 디자이너는 두 번째 방법을보다 자연스러운 방법으로 선택했습니다.

이 결정은 1999 년 7 월 7 일에 이루어졌으며 10 년 전에 부끄러웠다. 그 날의 노트에있는 주석은 아주 간단합니다. "스위치 케이스는 자체 선언 공간을 만들지 않습니다"라고 말한 다음 무엇이 작동하고 무엇이 작동하지 않는지를 보여주는 샘플 코드를 제공합니다.

이 특별한 날에 디자이너들의 생각에 대해 더 많은 것을 알기 위해서는 10 년 전에 생각했던 것에 대해 많은 사람들을 괴롭 히고 궁극적으로 사소한 문제에 대해 버그를 제기해야합니다. 나는 그렇게하지 않을 것입니다.

요컨대, 한 가지 방법을 선택해야 할 특별한 이유는 없습니다. 둘 다 장점이 있습니다. 언어 디자인 팀은 하나를 선택해야했기 때문에 한 가지 방법을 선택했습니다. 그들이 선택한 것은 나에게 합리적인 것 같습니다.

따라서 Eric Lippert보다 1999 년 C # 개발자 팀에 더 많은 시간을 보내지 않으면 정확한 이유를 알 수 없습니다!


downvoter는 아니지만 이것은 어제 질문에 대한 의견이었습니다 .
제시 C. 슬라이서

4
나는 그것을 보았지만 주석 작성자가 답변을 게시하지 않았기 때문에 링크의 내용을 인용하여 답변을 명시 적으로 작성하는 것이 좋습니다. 그리고 나는 downvotes를 상관하지 않습니다! OP는 "방법"이 아니라 역사적인 이유를 묻습니다.
Cyril Gandon

5

설명은 간단합니다. C와 비슷하기 때문입니다. C ++, Java 및 C #과 같은 언어는 친숙 함을 위해 switch 문의 구문과 범위를 복사했습니다.

(다른 답변에서 언급했듯이 C # 개발자는 사례 범위와 관련하여 이러한 결정을 내리는 방법에 대한 문서를 가지고 있지 않지만 C # 구문에 대한 명시되지 않은 원칙은 다르게 할 이유가 없다면 Java를 복사한다는 것입니다. )

C에서 사례 진술은 goto-labels와 유사합니다. switch 문은 실제로 계산 된 goto에 대한 더 좋은 구문입니다 . 케이스 는 스위치 블록 의 진입 점 을 정의합니다 . 명시적인 종료가 없으면 기본적으로 나머지 코드가 실행됩니다. 따라서 동일한 범위를 사용하는 것이 좋습니다.

(보다 근본적으로 Goto는 구조화되어 있지 않습니다. 코드 섹션 을 정의하거나 구분하지 않고 점프 포인트 만 정의하므로 goto 레이블 범위를 도입 할 수 없습니다 .)

C #은 구문을 유지하지만 비어 있지 않은 각 경우 절 뒤에 종료를 요구하여 "fall through"에 대한 보호 조치를 도입합니다. 그러나 이것은 스위치에 대한 우리의 생각을 변화시킵니다! 케이스는 이제 if-else의 분기와 같은 대체 분기 입니다. 이는 각 분기가 if-clauses 또는 iteration 절과 같이 자체 범위를 정의 할 것으로 예상한다는 의미입니다.

간단히 말해 : 사례는 C와 같은 방식이므로 동일한 범위를 공유합니다. 그러나 사례를 goto 대상이 아닌 대체 브랜치로 생각하기 때문에 C #에서는 이상하고 일치하지 않습니다.


1
" 사례가 goto 대상이 아닌 대체 브랜치로 생각되기 때문에 C #에서는 이상하고 일관성이없는 것 같습니다 ." C #에서 왜 잘못 느끼는지에 대한 미학적 이유를 설명해 주신 +1
pswg

4

범위를 보는 간단한 방법은 범위를 블록별로 고려하는 것 {}입니다.

switch블록을 포함하지 않기 때문에 다른 범위를 가질 수 없습니다.


3
무엇에 대해 for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */? 블록은 없지만 범위가 다릅니다.
svick

@ svick : 내가 말했듯이, for진술에 의해 만들어진 블록이 있습니다. switch최상위 수준에서 각 사례에 대한 블록을 만들지 않습니다. 유사점은 각 명령문이 하나의 블록을 작성한다는 것입니다 (명령문으로 계산 switch).
Guvante

1
그렇다면 왜 case대신에 셀 수 없었 습니까?
svick

1
@svick : case선택적으로 블록을 추가하기로 결정하지 않는 한 블록을 포함하지 않기 때문입니다. for추가하지 않는 한 다음 명령문에 대해 지속 {}되므로 항상 블록을 갖지만 case실제 블록, switch명령문 을 떠나게 할 때까지 지속됩니다 .
Guvante
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.