1.0이 아닌 열거 형 값에 0.0을 할당 할 수있는 이유


90

호기심에서 : 왜 1.0이 아닌 열거 형인 변수에 0.0을 할당 할 수 있습니까? 다음 코드를 살펴보십시오.

public enum Foo
{
    Bar,
    Baz
}

class Program
{
    static void Main()
    {
        Foo value1 = 0.0;
        Foo value2 = 1.0;   // This line does not compile
        Foo value3 = 4.2;   // This line does not compile
    }
}

숫자 유형과 열거 값 간의 변환은 캐스트를 통해서만 허용된다고 생각했습니다. 즉, Foo를 작성 value2 = (Foo) 1.0;하여 두 번째 줄 Main이 컴파일 될 수 있습니다. 0.0C # 의 값 에 예외가있는 이유는 무엇 입니까?


17
저에게는 이상 합니다. 이중 리터럴 0.0을 사용자 정의 열거 형에 할당 할 수 있습니다 . 사용자 정의 열거 형에 리터럴을 할당 할 수 없다는 것은 아닙니다 1.0.
Ilya Ivanov 2014 년

2
컴파일러가 0대신 처리하고 있다고 생각합니다 . 한 번 비슷한 질문이 있었는데 롤링이 여기에 훌륭한 답변을 올렸 습니다 .
Amicable

2
IdeOne 은 그것을 컴파일하지 않습니다.
Johnny Mopp 2014 년

2
관련 질문 : 이상한 (아마도
틀린가

답변:


98

0.0을 사용할 수있는 버그입니다. 컴파일러는 값이 0 인 모든 상수 식을 암시 적으로 0으로 처리합니다.

이제 컴파일러가 C # 5 사양의 섹션 6.1.3에 따라 0 의 상수 식에서 열거 형으로 의 암시 적 변환을 허용하는 것이 습니다 int.

암시 적 열거 변환은 decimal-integer-literal 0이 모든 열거 형 및 기본 유형이 열거 형인 모든 nullable-type으로 변환되도록 허용합니다. 후자의 경우 변환은 기본 열거 형 형식으로 변환하고 결과를 래핑하여 평가됩니다 (§4.1.10).

이전에 C # 팀과 이야기를 나눴습니다. 우연히 0.0 (실제로 0.0m 및 0.0f)에서 열거 형 값으로 변환 하는 것을 제거하고 싶었지만 불행히도 코드가 너무 많이 손상 되었음에도 불구하고 애초에 허용되어서는 안됩니다.

모노 mcs는 있지만 컴파일러는,이 부동 소수점 변환을 모두 금지 않습니다 수 있습니다 :

const int Zero = 0;
...

SomeEnum x = Zero;

그것이 Zero상수 표현이지만 십진 정수 리터럴이 아니라는 사실에도 불구하고 .

값이 0 인 정수 상수 표현식 (예 : 모방 mcs) 을 허용하도록 앞으로 C # 사양이 변경되는 것을보고 놀라지 않을 것입니다 . 그러나 부동 소수점 변환이 공식적 으로 정확할 것이라고 기대하지는 않습니다 . (물론 C #의 미래를 예측하는 것에 대해 이전에 틀 렸습니다 ...)


3
사양 당, 단지로 의미있어 문자 그대로 그것을 거부해야 그래서 0 1-1상수의 - int식을 값으로 0. 그러나 당신이 관찰 한 바와 같이 컴파일러는 여기의 사양과 일치하지 않습니다.
Damien_The_Unbeliever

4
it broke too much code-그런 코드를 작성하는 이유를 상상하기 정말 어렵습니다.
Ilya Ivanov 2014 년

1
@ObsidianPhoenix : 무슨 말인지 잘 모르겠습니다. 정확히 동일합니다 : SomeEnum x = (SomeEnum) 0;. 명명 된 0 값이 있는지 여부가 바로 그 경우입니다.
Jon Skeet 2014 년

2
@ObsidianPhoenix : 글쎄요, 값이 Test.Foo0이 아니라 1 이기 때문에 ... 다시 말하지만, 그것은 당신이 쓴 것과 똑같습니다. Test v1 = (Test) 0;그리고 그 동작은 열거 형에서 명명 된 값이 아닌 모든 값에 적용됩니다.
Jon Skeet 2014 년

2
@JonSkeet이 Roslyn에서 수정 될 예정입니까?
Max

98

Jon의 대답이 맞습니다. 나는 그것에 다음과 같은 점을 추가 할 것이다.

  • 이 어리 석고 부끄러운 버그를 일으켰습니다. 많은 사과드립니다.

  • 이 버그는 컴파일러에서 "expression is zero"술어의 의미를 오해하여 발생했습니다. 실제로 "이 유형의 기본값입니까?"라는 줄을 따라 더 많은 것을 검사 할 때 정수 0이 같음 만 검사한다고 믿었습니다. 사실, 이전 버전의 버그에서는 실제로 모든 유형의 기본값을 열거 형에 할당 할 수있었습니다! 이제는 숫자의 기본값 일뿐입니다. (수업 : 헬퍼 술어의 이름을 신중하게 지정하십시오.)

  • 내가 엉망으로 구현하려고 시도한 동작은 사실 약간 다른 버그에 대한 해결 방법이었습니다. https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-onehttps://docs.microsoft 에서 전체 끔찍한 이야기를 읽을 수 있습니다. .com / en-us / archive / blogs / ericlippert / the-root-of-all-evil-part-two (강의 : 이전 버그를 수정하면서 새로운 더 나쁜 버그를 도입하는 것은 매우 쉽습니다.)

  • C # 팀은 강력한 이점이없는 기존 코드를 손상시킬 위험이 너무 높기 때문에이 버그가있는 동작을 수정하기보다는 숨기기로 결정했습니다. (강의 : 처음부터 올바르게!)

  • 이 동작을 유지하기 위해 Roslyn에서 작성한 코드 IsConstantNumericZerohttps://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs 의 메서드 에서 찾을 수 있습니다. -Roslyn 행동이 정확히 무엇인지에 대한 자세한 내용은 그것을 참조하십시오. 저는 Conversions 디렉토리에 거의 모든 코드를 작성했습니다. 주석의 사양과 C #이 어떻게 다른지에 대한 흥미로운 사실이 많이 있으므로 모든 내용을 읽어 보시기 바랍니다. 쉽게 찾을 수 있도록 각각 SPEC VIOLATION으로 장식했습니다.

한 가지 더 주목할 점 : C #에서는 0 여부에 관계없이 열거 형 이니셜 라이저에서 모든 열거 형 값 을 사용할 수도 있습니다 .

enum E { A = 1 }
enum F { B = E.A }  // ???

사양은 이것이 합법적이어야하는지 아닌지에 대해 다소 모호하지만, 이것은 오랫동안 컴파일러에 있었기 때문에 새로운 컴파일러가 동작을 유지할 가능성이 높습니다.


10
이것은 정말 멋지다. 마침내 당신이 작성한 코드를 보게되었다. Roslyn 소스 코드가 오픈 소스라는 것은 대단합니다. 이제 변경 내역을 제공하지 않는 타당한 이유 (기술적 / 법적)가 있음을 완전히 이해하지만 코드가 어떻게 발전했는지 확인하기 위해 변경 내역을 보는 것은 정말 멋 졌을 것입니다.
SolutionYogi

The C# team decided to enshrine this buggy behaviour rather than fixing it because the risk of breaking existing code for no compelling benefit was too high.나는이 행동에 의존하는 사람들이 많지 않을 것이라고 생각하며, 그냥 고치는 것이 더 나을 수도있는 이러한 이상한 점 중 하나입니다. 그러나 실제로는 큰 해를 끼치 지 않습니다 (사양을 구현하는 프로젝트 제외).
Aidiakapi

5
@Aidiakapi : 실제로 영향을받는 사람의 수는 적어야합니다. 0이 아닙니다. C # 팀은 주요 변경 사항을 매우 중요하게 생각합니다. 그것은을 위해 쉽게 당신이 그것을 수정을하는 것이 좋습니다 말을; 당신의 부사장에게 전화를해서 아무런 혜택도주지 않는 사소한 변화로 인해 시스템 통합이 하루 지연되었다고 불평하는 분노한 고객을 처리 할 필요가 없습니다.
Eric Lippert

3
더 나빠집니다. 이러한 모든 주요 변경 사항은 (이상적으로) Microsoft의 프레임 워크 마이그레이션 가이드에 나열됩니다. 이 목록이 길수록 사용자는 애플리케이션을 마이그레이션하는 것을 더 주저하게됩니다. 따라서 사소한 변경으로 인해 다음이 발생합니다. 1. 일부 애플리케이션이 중단됩니다. 2. 소수의 사용자가 업그레이드를 거부합니다 (문제가 그들에게 영향을 미치지 않더라도). 3. 소수의 사용자가 주요 변경 사항이 영향을 미치는지 평가하는 데 자원을 낭비합니다. 4. # 1, # 2, # 3의 사용자가 다른 사람에게 불만을 표시합니다.
Brian

@EricLippert "사양이 다소 모호하다"면 사양을 업데이트하는 것이 합리적이지 않습니까? (진짜 질문!)
James

10

C #의 열거 형은 정의에 따른 정수 값입니다. 일관성을 위해 C #은 이러한 할당을 허용해서는 0.0안되지만 자동으로 integer로 처리됩니다 0. 이것은 아마도 C의 홀드 오버 일 것입니다. 여기서 리터럴 0은 특별히 처리되었으며 정수, 부동 소수점 숫자, 널 포인터 등 주어진 유형을 본질적으로 취할 수 있습니다.


3
질문은 입니까? 당신은에 가면 IL그것을 스택에 정수 값을 추진하고있다 -IL_0001: ldc.i4.0
일리야 이바노프을

@IlyaIvanov 업데이트를 참조하십시오. 그러나 솔직히 대답은“합당한 이유가 없다”입니다.
Konrad Rudolph

2
나는 이것이 C # spec 을 보면 합법적이지 않지만 MS가 생산 한 C # 컴파일러를 보면 이것이 하는 경우 중 하나라고 생각합니다 .
Damien_The_Unbeliever

3

enum 은 실제로 (지원하는 모든 언어에서) 숫자 값이 아닌 의미 있고 고유 한 문자열 (레이블)로 작업하는 방법이되도록 의도되었습니다. 따라서 예제에서 Foo 열거 데이터 유형을 처리 할 때 BarBaz 만 사용해야 합니다. 많은 컴파일러가 정수를 사용할 수 있도록 허용하더라도 (열거 형은 일반적으로 내부적 으로 정수임) 정수 를 사용 (비교 또는 할당)해서는 안됩니다 .이 경우 0.0은 컴파일러에서 부주의하게 0으로 처리됩니다.

개념적으로, 열거 된 값에 정수 n 을 추가하거나 , n 값을 더 아래로 가져 오거나, val2 - val1 을 사용하여 얼마나 멀리 떨어져 있는지 확인하는 것이 좋습니다 . 그러나 언어 사양에서 명시 적으로 허용하지 않는 한, I 그것을 피할 것입니다. (열거 형 값을 사용할 수있는 방식에서 C 포인터와 같은 것으로 생각하십시오.) 열거 형을 부동 소수점 숫자와 그 사이에 고정 된 증분으로 구현할 수없는 이유는 없지만 들어 본 적이 없습니다. 이것은 모든 언어로 이루어집니다.


나는 C #에서 그런 식으로 열거 형을 사용하지 않는다는 것을 알고 있지만이 수수께끼를 발견하고 0.0이 작동하는 이유를 알고 싶었지만 1.0은 작동하지 않습니다. IL 코드가 for Foo v1 = 0.0;와 동일 하다는 것을 알 수 있기 때문에 C # 컴파일러와 함께해야한다는 것을 알고있었습니다 Foo v2 = Foo.Bar.
feO2x
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.