왜이 (null ||! TryParse) 조건이 "할당되지 않은 지역 변수 사용"을 초래합니까?


98

다음 코드 는 할당되지 않은 지역 변수 "numberOfGroups"를 사용합니다 .

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

그러나이 코드는 잘 작동합니다 ( ReSharper 는 이 코드 = 10가 중복 된다고 말합니다 ).

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

내가 뭔가를 놓쳤거나 컴파일러가 내 마음에 들지 ||않습니까?

나는 이것을 dynamic문제 를 일으키는 것으로 좁혔습니다 ( options위 코드에서 동적 변수였습니다). 질문은 여전히 ​​남아 있습니다. 왜 이렇게 할 수 없습니까?

이 코드 컴파일 되지 않습니다 .

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        dynamic myString = args[0];

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

그러나이 코드 다음을 수행합니다 .

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        var myString = args[0]; // var would be string

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

dynamic이 요인이 될 줄 은 몰랐습니다 .


out매개 변수에 전달 된 값 을 입력으로 사용하지 않는다는 것을 알 정도로 똑똑하다고 생각하지 마십시오
Charleh

3
여기에 제공된 코드는 설명 된 동작을 보여주지 않습니다. 잘 작동합니다. 우리가 직접 컴파일 할 수 있다는 것을 설명 하는 동작실제로 보여주는 코드를 게시하십시오 . 전체 파일을주세요.
Eric Lippert 2013

8
아, 이제 뭔가 흥미로운 것이 생겼습니다!
Eric Lippert 2013

1
컴파일러가 이것으로 혼란스러워하는 것은 그리 놀라운 일이 아닙니다. 동적 호출 사이트의 도우미 코드에는 out매개 변수 할당을 보장하지 않는 제어 흐름이있을 수 있습니다. 문제를 피하기 위해 컴파일러가 어떤 도우미 코드를 생성해야하는지 또는 가능하다면 어떤 코드를 작성해야하는지 고려하는 것은 확실히 흥미 롭습니다.
CodesInChaos 2013

1
언뜻보기에 이것은 확실히 버그처럼 보입니다.
Eric Lippert 2013

답변:


73

이것이 컴파일러 버그라고 확신합니다. 좋은 발견!

편집 : Quartermeister가 보여주는 것처럼 버그가 아닙니다. dynamic은 초기화되지 않는 이상한 true연산자를 구현할 수 있습니다 y.

다음은 최소한의 재현입니다.

class Program
{
    static bool M(out int x) 
    { 
        x = 123; 
        return true; 
    }
    static int N(dynamic d)
    {
        int y;
        if(d || M(out y))
            y = 10;
        return y; 
    }
}

나는 그것이 불법이어야하는 이유를 알지 못한다. dynamic을 bool로 바꾸면 잘 컴파일됩니다.

저는 실제로 내일 C # 팀과 만납니다. 나는 그들에게 그것을 언급 할 것이다. 오류에 대해 사과드립니다!


6
나는 내가 미쳐 가지 않는다는 것을 알게되어 기쁩니다. :) 나는 이후로 TryParse에만 의존하도록 코드를 업데이트 했으므로 지금은 준비되었습니다. 귀하의 통찰력에 감사드립니다!
Brandon Martinez

4
@NominSim : 런타임 분석이 실패했다고 가정하면 로컬을 읽기 전에 예외가 발생합니다. 런타임 분석이 성공했다고 가정합니다. 그러면 런타임 에 d가 true이고 y가 설정되거나 d가 false이고 M이 y를 설정합니다. 어느 쪽이든, y가 설정됩니다. 분석이 런타임까지 연기된다는 사실은 아무것도 변경하지 않습니다.
Eric Lippert 2013

2
궁금한 사람이있는 경우 : 방금 확인한 결과 Mono 컴파일러가 올바르게 작동합니다. imgur.com/g47oquT
Dan Tao

17
의 값이 d오버로드 된 true연산자 가있는 유형일 수 있으므로 컴파일러 동작이 실제로 옳다고 생각합니다 . 두 가지 모두 사용하지 않는 예제와 함께 답변을 게시했습니다.
Quartermeister

2
@Quartermeister이 경우 Mono 컴파일러가 잘못되었습니다 :)
porges

52

동적 표현식의 값이 오버로드 된 true연산자 가있는 유형 인 경우 변수가 할당 해제 될 수 있습니다 .

||운영자는 호출합니다 true오른쪽을 평가할지 여부를 결정하는 연산자를 다음 if문은 호출합니다 true몸을 평가할지 여부를 결정하는 연산자. normal의 bool경우 항상 동일한 결과를 반환하므로 정확히 하나가 평가되지만 사용자 정의 연산자의 경우 그러한 보장이 없습니다!

Eric Lippert의 재현을 바탕으로 다음은 경로가 실행되지 않고 변수가 초기 값을 갖는 경우를 보여주는 짧고 완전한 프로그램입니다.

using System;

class Program
{
    static bool M(out int x)
    {
        x = 123;
        return true;
    }

    static int N(dynamic d)
    {
        int y = 3;
        if (d || M(out y))
            y = 10;
        return y;
    }

    static void Main(string[] args)
    {
        var result = N(new EvilBool());
        // Prints 3!
        Console.WriteLine(result);
    }
}

class EvilBool
{
    private bool value;

    public static bool operator true(EvilBool b)
    {
        // Return true the first time this is called
        // and false the second time
        b.value = !b.value;
        return b.value;
    }

    public static bool operator false(EvilBool b)
    {
        throw new NotImplementedException();
    }
}

8
여기서 잘 했어요. 저는 이것을 C # 테스트 및 디자인 팀에 전달했습니다. 내일 볼 때 댓글이 있는지 볼게요.
Eric Lippert 2013

3
이것은 나에게 매우 이상합니다. 왜 d두 번 평가 해야 합니까? (나는 그것이 명확하게 이의를 제기하고 있지 않다 입니다 당신이 보여준대로.) 나는 것의 평가 결과를 예상 한 true(첫 번째 운영자 호출에서에 의해 원인을 ||)를에 "함께 전달"하는 if문. 예를 들어, 거기에 함수 호출을 넣으면 확실히 일어날 일입니다.
Dan Tao

3
@DanTao : d예상대로 식은 한 번만 평가됩니다. 그것은이다 true한 번, 두 번 호출되는 것 운영자 ||와 한 번 if.
Quartermeister

2
@DanTao : 별도의 문에 var cond = d || M(out y); if (cond) { ... }. 먼저 객체 참조 d를 얻기 위해 평가 합니다 EvilBool. 를 평가하려면 ||먼저 EvilBool.true해당 참조를 호출 합니다. true를 반환하므로 단락하고을 호출하지 않은 M다음 참조를에 할당합니다 cond. 그런 다음 if성명으로 이동 합니다. if문을 호출하여 상태를 평가합니다 EvilBool.true.
Quartermeister

2
이제 이것은 정말 멋지다. 참 또는 거짓 연산자가 있는지 전혀 몰랐습니다.
IllidanS4는 Monica가 2013

7

MSDN에서 (강조 내) :

동적 유형을 사용하면 컴파일 타임 유형 검사우회 하는 작업이 수행 됩니다. 대신 이러한 작업은 런타임에 해결됩니다 . 동적 유형은 Office Automation API와 같은 COM API 및 IronPython 라이브러리와 같은 동적 API 및 HTML DOM (문서 개체 모델)에 대한 액세스를 단순화합니다.

동적 유형은 대부분의 상황에서 유형 객체처럼 작동합니다. 그러나 동적 형식의 식을 포함하는 작업은 컴파일러에서 확인되거나 형식이 확인되지 않습니다.

컴파일러는 동적 유형의 표현식이 포함 된 연산을 유형 검사하거나 확인하지 않으므로 TryParse().


첫 번째 조건이 충족되면 numberGroups( if true블록에서) 할당되고 , 그렇지 않은 경우 두 번째 조건은 할당을 보장합니다 (를 통해 out).
leppie 2013

1
그것은 흥미로운 생각 이지만 코드는 myString == null(만 의존) 없이 잘 컴파일됩니다 TryParse.
Brandon Martinez

1
@leppie 요점은 첫 번째 조건 (실제로 전체 if표현식)이 dynamic변수를 포함하기 때문에 컴파일 시간에 해결되지 않는다는 것입니다 (따라서 컴파일러는 이러한 가정을 할 수 없습니다).
NominSim 2013

@NominSim : 나는 당신의 요점을 본다 :) +1 컴파일러의 희생이 될 수 있지만 (C # 규칙 위반) 다른 제안은 버그를 암시하는 것 같습니다. Eric의 스 니펫은 이것이 희생이 아니라 버그임을 보여줍니다.
leppie 2013

@NominSim 이것은 옳을 수 없습니다; 특정 컴파일러 함수가 지연되었다고해서 모든 함수가 지연되는 것은 아닙니다. 약간 다른 상황에서 컴파일러는 동적 표현식이 있음에도 불구하고 문제없이 명확한 할당 분석을 수행한다는 것을 보여주는 많은 증거가 있습니다.
dlev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.