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

나는이 코드를 작성했다 :

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자신의 로컬 범위를 블록에 제공하지 않았 습니까? 이에 대한 역사적 또는 기술적 이유가 있습니까?



답변

다른 모든 경우에“정상”로컬 변수의 범위는 중괄호 ( )로 구분 된 블록 이기 때문에 좋은 이유라고 생각합니다 {}. 일반적이지 않은 지역 변수는 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언급 된 다른 모든 구문과 달리 로컬 변수 감소에 대한 특별한 구문이 없으므로 블록이 명시 적으로 언급 된 이유를 완전히 확신 하지는 못합니다.)


답변

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

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.");
}


답변

에릭 리퍼 (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 # 개발자 팀에 더 많은 시간을 보내지 않으면 정확한 이유를 알 수 없습니다!


답변

설명은 간단합니다. 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 #에서는 이상하고 일치하지 않습니다.


답변

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

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


답변