Roslyn이 코드를 컴파일하지 못했습니다.


95

프로젝트를 VS2013에서 VS2015로 마이그레이션 한 후 프로젝트가 더 이상 빌드되지 않습니다. 다음 LINQ 문에서 컴파일 오류가 발생합니다.

static void Main(string[] args)
{
    decimal a, b;
    IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
    var result = (from v in array
                  where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
                  orderby decimal.Parse(v)
                  select v).ToArray();
}

컴파일러는 오류를 반환합니다.

오류 CS0165 할당되지 않은 지역 변수 'b'사용

이 문제의 원인은 무엇입니까? 컴파일러 설정을 통해 수정할 수 있습니까?


11
@BinaryWorrier : 왜? 매개 변수 b를 통해 할당 한 후에 만 사용합니다 out.
Jon Skeet

1
VS 2015 문서에 따르면 "출력 인수로 전달 된 변수는 전달되기 전에 초기화 될 필요가 없지만 메서드가 반환되기 전에 값을 할당하려면 호출 된 메서드가 필요합니다." 그래서 이것은 버그처럼 보입니다. 예, 그것은 tryParse에 의해 초기화된다는 것이 보장됩니다.
Rup

3
오류에 관계없이이 코드는 out인수 에 대해 나쁜 모든 것을 예시했습니다 . TryParsenullable 값 (또는 동등한 값) 을 반환합니까 ?
Konrad Rudolph

1
@KonradRudolph이 where (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= b보이는 훨씬 더 나은
Rawling

2
참고로, 이것을 decimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;. 나는 이것을 제기 하는 Roslyn 버그를 열었습니다 .
Rawling 2015-08-12

답변:


112

이 문제의 원인은 무엇입니까?

나에게 컴파일러 버그처럼 보입니다. 적어도 그렇습니다. 있지만 decimal.TryParse(v, out a)decimal.TryParse(v, out b)표현이 동적으로 평가, 내가 예상 컴파일러는 여전히 이해가가 도달하는 시간으로 a <= b, 모두 ab확실히 할당됩니다. 동적 타이핑에서 생각 해낼 수있는 이상한 점이 있더라도 a <= bTryParse호출을 모두 평가 한 후에 만 평가할 것으로 기대 합니다.

그러나 연산자와 변환을 통해 까다로워서 충분히 교활하다면 A && B && C평가 A하고 평가 C하지 않는 표현식을 갖는 것이 완전히 가능하다는 것이 밝혀졌습니다 B. Neal Gafter의 독창적 인 예 는 Roslyn 버그 보고서 를 참조하세요 .

이 작업을 수행하는 dynamic것은 훨씬 더 어렵습니다. 피연산자가 동적 일 때 관련된 의미는 설명하기가 더 어렵습니다. 왜냐하면 오버로드 해결을 수행하려면 어떤 유형이 관련되어 있는지 알아보기 위해 피연산자를 평가해야하는데, 이는 직관적이지 않을 수 있습니다. 그러나 Neal은 컴파일러 오류가 필요함을 보여주는 예제를 다시 제시했습니다. 이것은 버그가 아니라 버그 수정 입니다. 그것을 증명 한 Neal에게 엄청난 찬사를 보냅니다.

컴파일러 설정을 통해 수정할 수 있습니까?

아니요,하지만 오류를 방지하는 대안이 있습니다.

첫째, 동적이되는 것을 막을 수 있습니다. 문자열 만 사용할 것이라는 것을 알고 있다면 범위 변수 에 (예 :) 유형을 사용 IEnumerable<string> 하거나 지정할 수 있습니다 . 그것이 제가 선호하는 옵션입니다.vstringfrom string v in array

당신이 경우 정말 그것을 동적를 계속해야, 단지 줄 b로 시작하는 값을 :

decimal a, b = 0m;

이것은 아무런 해를 끼치 지 않을 것입니다. 실제로 동적 평가가 미친 짓을하지 않는다는 것을 알고 있으므로 b사용하기 전에 값을 할당 하여 초기 값을 무의미하게 만듭니다.

또한 괄호 추가도 작동하는 것 같습니다.

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)

이는 다양한 오버로드 해결이 트리거되는 지점을 변경하고 컴파일러를 행복하게 만듭니다.

하나 여전히 남아있는 문제 일 -와 명확한 과제에 대한 사양의 규칙 &&운영자 필요성은 때에만 적용한다는 것은 명확히하는 &&연산자는 두과의 "일반"구현에 사용되는 bool피연산자. 다음 ECMA 표준을 위해 이것이 수정되었는지 확인하려고 노력할 것입니다.


네! IEnumerable<string>대괄호를 적용 하거나 추가하는 것이 저에게 효과적이었습니다. 이제 컴파일러가 오류없이 빌드됩니다.
ramil89

1
사용 decimal a, b = 0m;하면 오류가 제거되지만 out 값이 아직 계산되지 않았으므로 a <= b항상을 사용 0m합니다.
Paw Baltzersen 2015 년

12
@PawBaltzersen : 왜 그렇게 생각합니까? 그것은 항상 비교 전에 할당 될 것입니다. 단지 컴파일러가 어떤 이유로 (기본적으로 버그) 그것을 증명할 수 없다는 것입니다.
Jon Skeet

1
부작용이없는 구문 분석 방법 즉. decimal? TryParseDecimal(string txt)도 해결책이 될 수있다
자히르

1
게으른 초기화인지 궁금합니다. 그것은 "첫 번째 것이 사실이라면 나는 b할당되지 않을 수도 있다는 것을 의미하는 두 번째 것을 평가할 필요 가 없다"고 생각한다; 그게 잘못된 추론 알고 있지만 왜 괄호 수정이 ... 설명
durron597


16

버그 리포트를 열심히 공부했기 때문에 직접 설명하려고합니다.


상상은 T에 암시 적 캐스트와 일부 사용자 정의 유형 bool간의 대체 false하고 true,로 시작 false. 컴파일러가 아는 한 dynamic첫 번째 인수 &&는 해당 유형으로 평가 될 수 있으므로 비관적이어야합니다.

그런 다음 코드가 컴파일되도록하면 다음과 같은 일이 발생할 수 있습니다.

  • 동적 바인더가 첫 번째를 평가할 때 &&다음을 수행합니다.
    • 첫 번째 인수 평가
    • 그것은 T암시 적으로 그것을 bool.
    • 아,이므로 false두 번째 인수를 평가할 필요가 없습니다.
    • &&평가 결과를 첫 번째 인수로 만듭니다. (아니요, false어떤 이유로 든 아닙니다 .)
  • 동적 바인더가 두 번째를 평가할 때 &&다음을 수행합니다.
    • 첫 번째 인수를 평가하십시오.
    • 그것은 T암시 적으로 그것을 bool.
    • 아, true그래서 두 번째 인수를 평가하십시오.
    • ... 아 젠장, b할당되지 않았습니다.

요컨대, 변수가 "확실히 할당"되었는지 또는 "확실히 할당되지 않았는지"뿐만 아니라 " false문 뒤에 명확하게 할당"또는 "확실히 할당되었는지"를 알려주는 특별한 "정확한 할당"규칙이 있습니다. true문 이후 할당 ".

이것들은 &&and ||(and !and ??and ?:)를 처리 할 때 컴파일러가 복잡한 부울 표현식의 특정 분기에 변수가 할당 될 수 있는지 여부를 검사 할 수 있도록 존재합니다.

그러나 표현식의 유형이 boolean으로 유지되는 동안에 만 작동 합니다 . 표현의 한 부분 인 경우 dynamic(또는 부울이 아닌 정적 유형) 우리는 더 이상 안정적으로 발현이라고 말할 수 있습니다 true또는 false- 다음 번에 우리가 캐스팅 bool소요되는 지점을 결정, 그것의 마음을 변경되었을 수 있습니다.


업데이트 : 이제이 문제해결 되고 문서화 되었습니다 .

동적 표현식에 대해 이전 컴파일러에 의해 구현 된 명확한 할당 규칙은 확실히 할당되지 않은 변수를 읽을 수있는 코드의 경우를 허용했습니다. 이에 대한 보고서는 https://github.com/dotnet/roslyn/issues/4509 를 참조 하십시오 .

...

이러한 가능성 때문에 컴파일러는 val에 초기 값이없는 경우이 프로그램이 컴파일되는 것을 허용하지 않아야합니다. 이전 버전의 컴파일러 (VS2015 이전)에서는 val에 초기 값이없는 경우에도이 프로그램을 컴파일 할 수있었습니다. Roslyn은 이제 초기화되지 않은 변수를 읽으려는이 시도를 진단합니다.


1
다른 컴퓨터에서 VS2013을 사용하여 실제로 이것을 사용하여 할당되지 않은 메모리를 읽을 수있었습니다. 별로 흥미 진진하지 않습니다. :(
Rawling 2015-08-12

간단한 델리게이트로 초기화되지 않은 변수를 읽을 수 있습니다. out이있는 메서드에 가져 오는 대리자를 만듭니다 ref. 그것은 행복하게 할 것이고, 값을 변경하지 않고 할당 된 변수를 만들 것입니다.
IllidanS4은 모니카 다시 원하는

호기심에 C # v4로 해당 코드를 테스트했습니다. 하지만 호기심에서-컴파일러 는 암시 적 캐스트 연산자와 반대로 false/ 연산자를 어떻게 사용하기로 결정 true합니까? 로컬에서는 implicit operator bool첫 번째 인수를 호출 한 다음 두 번째 피연산자를 호출 operator false하고 첫 번째 피연산자를 호출 한 다음 첫 implicit operator bool번째 피연산자를 다시 호출 합니다 . 이것은 나에게 의미가 없습니다. 첫 번째 피연산자는 본질적으로 한 번 부울로 요약되어야합니다.

@Rob 이것이 dynamic, 체인 &&케이스입니까? 나는 기본적으로 (1) 첫 번째 인수를 평가합니다 (2) 암시 적 캐스트를 사용하여 단락 할 수 있는지 확인합니다 (3) 할 수 없으므로 두 번째 인수를 평가하십시오 (4) 이제 두 유형을 모두 알고 있습니다. 가장 좋은 &&것은 첫 번째 인수에 사용자 정의 &(5) 호출 연산자 false를 사용하여 단락을 수행 할 수 있는지 확인하는 것입니다. (6) 할 수 있습니다 (이유 falseimplicit bool동의하지 않음). 결과는 첫 번째 인수입니다. &&, (7) 암시 적 캐스트를 사용하여 단락 (다시) 할 수 있는지 확인합니다.
Rawling

@ IllidanS4 흥미롭게 들리지만 방법을 찾지 못했습니다. 스 니펫 좀 주 시겠어요?
Rawling 2015 년

15

이것은 버그가 아닙니다. 이 양식의 동적 표현이 이러한 out 변수를 할당되지 않은 상태로 둘 수있는 방법에 대한 예제는 https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713 을 참조 하십시오 .


1
내 대답이 수락되고 찬성도가 높기 때문에 해결 방법을 나타 내기 위해 편집했습니다. 이 모든 작업을 주셔서 감사합니다 - :) 나에게 내 실수를 설명 포함
존 소총
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.