null 참조가 불가능할 때 왜 역 참조 null 참조 경고가 발생합니까?


9

HNQ 에서이 질문 을 읽은 후에 C # 8의 Nullable Reference Types에 대해 읽고 몇 가지 실험을했습니다.

나는 누군가가 "컴파일러 버그를 발견했다!"라고 말할 때 10에서 9 번, 또는 더 자주 알고 있습니다. 이것은 실제로 의도적으로 설계된 것이며 자체 오해입니다. 그리고 오늘만이 기능을 살펴보기 시작 했으므로 분명히 잘 이해하지 못합니다. 이 방법으로 다음 코드를 살펴보십시오.

#nullable enable
class Program
{
    static void Main()
    {
        var s = "";
        var b = s == null; // If you comment this line out, the warning on the line below disappears
        var i = s.Length; // warning CS8602: Dereference of a possibly null reference
    }
}

나는 위의 링크 된 문서를 읽은 후, 나는 기대 s == null라인이 나에게 줄 수있는 모든 후 경고- s명확 Null을 허용하지 않는, 그래서 그것을 비교 null이해가되지 않습니다.

대신 다음 줄에 경고가 표시되며 경고는 s인간에게는 null 참조가 가능하지만 그렇지 않다는 것이 분명합니다.

더 이상 경고입니다 하지 우리가 비교하지 않는 경우 표시 snull.

나는 인터넷 검색을 수행하고 GitHub 문제에 부딪 혔습니다 . GitHub 문제 는 완전히 다른 것에 관한 것으로 밝혀졌지만 그 과정에서이 행동에 대해 더 많은 통찰력을 제공 한 기고자와 대화했습니다 (예 : "널 체크는 종종 유용한 방법입니다) 변수의 Null 허용 여부에 대한 그 이전의 추론을 다시 컴파일러를 말하는. " ). 그러나 여전히 주요 질문에 답하지 못했습니다.

나는 새로운 GitHub 이슈를 만들고, 매우 바쁘게 프로젝트 기여자들의 시간을 빼앗기지 않고 이것을 커뮤니티에 내놓고있다.

무슨 일이 일어나고 있으며 왜 그런지 설명해 주시겠습니까? 특히, 왜 경고는 생성되지 않습니다 s == null라인, 우리는 왜해야합니까 CS8602아닌 것 같아 때 null참조가 여기에있다? 연결된 GitHub 스레드에서 알 수 있듯이 nullability 유추가 방탄이 아닌 경우 어떻게 잘못 될 수 있습니까? 그 예는 무엇입니까?


컴파일러 자체는이 시점에서 변수 "s"가 null 일 수있는 동작을 설정하는 것 같습니다. 어쨌든 문자열이나 객체를 사용하는 경우 함수를 호출하기 전에 항상 확인해야합니다. "s? .Length"는 트릭을 수행하고 경고 자체는 사라져야합니다.
chg

1
@chg, 필요가 없을 것 ?때문에 s널 입력이 가능하지 않습니다. 단순히 우리와 비교하기에 어리석기 때문에 nullable이되지 않습니다 null.
Andrew Savinykh

나는 그것이 당신이 값이 null인지 검사를 추가하는 경우, 다음 컴파일러는 그 값이 있음을 '힌트'로 소요 단정 한 이전 질문 (죄송합니다, 그것을 찾을 수 없습니다) 다음 한 , null도를 심지어 경우 사실은 그렇지 않습니다.
stuartd

@stuartd, 그렇습니다, 그것이 보이는 것 같습니다. 이제 질문은이게 왜 유용한가요?
Andrew Savinykh

1
@chg, 그것이 질문 본문에서 내가 말하는 것입니다.
Andrew Savinykh

답변:


7

이것은 @stuartd가 링크 한 답변과 사실상 중복되므로 여기서는 자세한 내용을 다루지 않겠습니다. 그러나 문제의 근본 원인은 언어 버그 나 컴파일러 버그가 아니라 구현 된 그대로의 동작입니다. 변수의 null 상태를 추적합니다. 변수를 처음 선언하면 널이 아닌 값으로 명시 적으로 초기화하기 때문에 해당 상태는 NotNull입니다. 그러나 우리는 NotNull이 어디에서 왔는지 추적하지 않습니다. 예를 들어 이것은 사실상 동등한 코드입니다.

#nullable enable
class Program
{
    static void Main()
    {
        M("");
    }
    static void M(string s)
    {
        var b = s == null;
        var i = s.Length; // warning CS8602: Dereference of a possibly null reference
    }
}

두 경우 모두 명시 적으로 테스트 s합니다 null. 우리는 Mads가 https://stackoverflow.com/a/59328672/2672518 에서 대답 한 것처럼 흐름 분석의 입력으로 사용합니다 . 그 대답에서, 당신은 반환에 대한 경고를 받게됩니다. 이 경우, 아마도 널 참조를 역 참조했다는 경고가 표시됩니다.

단순히 우리와 비교하기에 어리석기 때문에 nullable이되지 않습니다 null.

그렇습니다. 컴파일러에게. 인간으로서 우리는이 코드를보고 null 참조 예외를 던질 수 없다는 것을 분명히 이해할 수 있습니다. 그러나 nullable 흐름 분석이 컴파일러에서 구현되는 방식으로는 불가능합니다. 우리는 가치가 어디에서 왔는가에 따라 상태를 추가하는이 분석에 대해 어느 정도의 개선을 논의했지만, 이것이 큰 이익이 아닌 구현에 많은 복잡성을 추가하기로 결정했습니다. 이것은 사용자가 a new또는 상수 값 으로 변수를 초기화 한 다음 null어쨌든 확인하는 경우에 유용 합니다.


감사합니다. 이것은 주로 다른 질문과의 유사성을 다루고 있습니다. 차이를 더 다루고 싶습니다. 예를 들어 왜 s == null경고가 나타나지 않습니까?
Andrew Savinykh 2014

또한 나는 방금 #nullable enable; string s = "";s = null;활성화 된 널 어노테이션 컨텍스트에서 널 (null)이 아닌 참조에 널을 지정할 수있는 구현의 컴파일 및 작동 (여전히 경고를 생성 함) 구현의 이점은 무엇입니까?
Andrew Savinykh 2014

Mad의 답변은 "[컴파일러]가 개별 변수 상태 간의 관계를 추적하지 않는다"는 사실에 초점을 맞추고 있습니다.이 예에서는 별도의 변수가 없으므로이 경우에 나머지 Mad의 답변을 적용하는 데 문제가 있습니다.
Andrew Savinykh 2014

말할 것도없이 기억하지만, 나는 비판하지 않고 배우기 위해 여기에 있습니다. 2001 년 처음 나온 시점부터 C #을 사용해 왔습니다. 언어에 익숙하지 않더라도 컴파일러의 동작 방식은 놀랍습니다. 이 질문의 목표는 왜이 행동이 인간에게 유용한 지에 대한 이론적 근거를 제시하는 것입니다.
Andrew Savinykh 2014

확인해야 할 유효한 이유가 있습니다 s == null. 예를 들어, 공개 메소드에 있고 매개 변수 유효성 검증을 수행하려고합니다. 또는 잘못 주석이 달린 라이브러리를 사용하고 있으며 버그가 수정 될 때까지 선언되지 않은 null을 처리해야합니다. 두 경우 모두 우리가 경고하면 나쁜 경험이 될 것입니다. 할당 허용과 관련하여 로컬 변수 주석은 읽기 전용입니다. 런타임에 전혀 영향을 미치지 않습니다. 실제로 모든 경고를 단일 오류 코드로 작성하여 코드 이탈을 줄이려면 해제 할 수 있습니다.
333fred

0

무효 성 추론이 방탄이 아닌 경우 [..] 어떻게 잘못 될 수 있습니까?

가능한 빨리 C # 8의 nullable 참조를 행복하게 채택했습니다. ReSharper의 [NotNull] (기타) 표기법을 사용하는 데 익숙해지면서 둘 사이에 약간의 차이가 있음을 알았습니다.

C # 컴파일러는 속일 수 있지만 주의를 기울이는 경향이 있습니다 (보통 항상 그런 것은 아닙니다).

향후 방문자를위한 참고 자료로, 컴파일러가 혼란스러워지는 시나리오를 보았습니다 (이러한 사례는 모두 의도적 인 것으로 가정).

  • 널 용서 null . 역 참조 경고를 피하기 위해 종종 사용되지만 객체는 null을 허용하지 않습니다. 발을 두 신발에 넣고 싶어하는 것 같습니다.
    string s = null!; //No warning

  • 표면 분석 . ReSharper ( 코드 주석 사용)와는 달리 C # 컴파일러는 여전히 nullable 참조를 처리하기 위해 전체 범위의 속성을 지원하지 않습니다.
    void DoSomethingWith(string? s)
    {    
        ThrowIfNull(s);
        var split = s.Split(' '); //Dereference warning
    }

그러나 경고를 제거하는 Null 허용 여부를 확인하기 위해 일부 구문을 사용할 수 있습니다.

    public static void DoSomethingWith(string? s)
    {
        Debug.Assert(s != null, nameof(s) + " != null");
        var split = s.Split(' ');  //No warning
    }

또는 (아직도 멋진) 속성 ( 여기에서 모두 찾기) ) :

    public static bool IsNullOrEmpty([NotNullWhen(false)] string? value)
    {
        ...
    }

  • 민감한 코드 분석 . 이것이 당신이 밝힌 것입니다. 컴파일러는 작동하기 위해 가정해야하며 때로는 인간에게는 반 직관적 인 것처럼 보일 수 있습니다.
    void DoSomethingWith(string s)
    {    
        var b = s == null;
        var i = s.Length; // Dereference warning
    }

  • 제네릭 문제 . 여기에서 묻고 여기에서 아주 잘 설명 했습니다 (이전과 같은 기사, "T의 문제?"). 제네릭은 참조와 값을 모두 만족시켜야하기 때문에 복잡합니다. 가장 큰 차이점은string? 은 단지 문자열이고 int?a가 Nullable<int>되어 컴파일러가 실질적으로 다른 방식으로 처리하도록한다는 것입니다. 또한 컴파일러는 안전한 경로를 선택하여 원하는 것을 지정하도록합니다.
    public interface IResult<out T> : IResult
    {
        T? Data { get; } //Warning/Error: A nullable type parameter must be known to be a value type or non-nullable reference type.
    }

구속 조건 제공 :

    public interface IResult<out T> : IResult where T : class { T? Data { get; }}
    public interface IResult<T> : IResult where T : struct { T? Data { get; }}

그러나 제약 조건을 사용하지 않고 '?'를 제거하면 Data에서 'default'키워드를 사용하여 null 값을 여전히 넣을 수 있습니다.

    [Pure]
    public static Result<T> Failure(string description, T data = default)
        => new Result<T>(ResultOutcome.Failure, data, description); 
        // data here is definitely null. No warning though.

마지막은 안전하지 않은 코드를 작성할 수 있기 때문에 까다로워 보입니다.

이것이 누군가를 돕기를 바랍니다.


nullable 특성에 대한 문서를 docs.microsoft.com/en-us/dotnet/csharp/nullable-attributes 에 제공하는 것이 좋습니다 . 표면 분석 섹션 및 제네릭과 같은 일부 문제를 해결합니다.
333fred

링크 @ 333fred에 감사드립니다. 사용할 수있는 속성이 있지만 게시 한 문제를 해결하는 속성 ( 널이 아닌지 ThrowIfNull(s);확인하는 것 s)이 존재하지 않습니다. 또한이 기사는 nullable이 아닌 제네릭 을 처리하는 방법을 설명 하지만 null 값을 가지지 만 경고는하지 않는 컴파일러를 "속일 수있는"방법을 보여주었습니다.
Alvin Sartor

실제로 속성이 존재합니다. 문서에 버그를 제출하여 추가했습니다. 찾고 있습니다 DoesNotReturnIf(bool).
333fred

@ 333fred 실제로 나는 더 비슷한 것을 찾고 DoesNotReturnIfNull(nullable)있습니다.
Alvin Sartor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.