다음 코드를 고려하십시오.
using System;
#nullable enable
namespace Demo
{
public sealed class TestClass
{
public string Test()
{
bool isNull = _test == null;
if (isNull)
return "";
else
return _test; // !!!
}
readonly string _test = "";
}
}
이것을 빌드하면로 표시된 줄 !!!
에 컴파일러 경고가 표시됩니다 warning CS8603: Possible null reference return.
.
_test
읽기 전용이며 null이 아닌 것으로 초기화 되면 다소 혼란 스럽습니다 .
코드를 다음과 같이 변경하면 경고가 사라집니다.
public string Test()
{
// bool isNull = _test == null;
if (_test == null)
return "";
else
return _test;
}
누구든지이 행동을 설명 할 수 있습니까?
답변
nullable 흐름 분석 은 변수 의 null 상태 를 추적하지만 변수 값과 같은 다른 상태는 추적하지 않으며 bool
( isNull
위와 같이) 개별 변수 상태 간의 관계 (예 : isNull
및 _test
) 는 추적하지 않습니다 .
실제 정적 분석 엔진은 아마도 이러한 작업을 수행 할 수도 있지만 어느 정도는 “휴리스틱”또는 “임의”일 수도 있습니다. 규칙을 따를 필요는 없으며, 시간이 지남에 따라 규칙이 변경 될 수도 있습니다.
C # 컴파일러에서 직접 할 수있는 것은 아닙니다. nullable 경고에 대한 규칙은 Jon의 분석에서 볼 수 있듯이 매우 정교하지만 규칙이므로 추론 할 수 있습니다.
이 기능을 출시함에 따라 대부분 적절한 균형을 잡은 것처럼 느껴지지만 어색한 곳이 몇 군데 있으며 C # 9.0의 위치를 다시 방문하게됩니다.
답변
나는 합리적인 추측을 할 수있다 여기에 무슨 일이 일어나고 있는지에 관해서는,하지만 조금 복잡 모두이다 : 그것은 포함 널 상태와 널 (null)이 초안 사양에 설명 된 내용 추적 . 기본적으로 반환하려는 지점에서 컴파일러는 식의 상태가 “not null”이 아닌 “null 일 수 있음”인지 경고합니다.
이 답변은 “여기에 결론이 있습니다”라기보다는 다소 이야기 형식으로되어 있습니다. 그 방법이 더 유용하기를 바랍니다.
필드를 제거하여 예제를 약간 단순화하고 다음 두 서명 중 하나를 사용하는 방법을 고려합니다.
public static string M(string? text)
public static string M(string text)
아래 구현에서 각 메소드에 다른 번호를 지정하여 특정 예제를 명확하게 참조 할 수 있습니다. 또한 모든 구현이 동일한 프로그램에 존재할 수 있습니다.
각 경우 아래 설명에서 우리는 다양한 일을하지만 복귀를 시도하게 될 겁니다 text
– 그것의 널 상태 그래서text
.
무조건 반환
먼저 직접 반환 해 봅시다.
public static string M1(string? text) => text; // Warning
public static string M2(string text) => text; // No warning
지금까지는 간단합니다. 메소드 시작시 매개 변수의 널 입력 가능 상태는 유형 인 경우 “널 (NULL) 일 수 string?
있으며” 유형 인 경우 “널 (null)”이 아닙니다 string
.
간단한 조건부 반환
이제 if
명령문 조건 자체 에서 null을 확인하십시오 . (나는 조건부 연산자를 사용할 것입니다.이 연산자는 동일한 효과가 있다고 생각하지만 질문에 더 충실하고 싶었습니다.)
public static string M3(string? text)
{
if (text is null)
{
return "";
}
else
{
return text; // No warning
}
}
public static string M4(string text)
{
if (text is null)
{
return "";
}
else
{
return text; // No warning
}
}
if
조건 자체가 널 (NULL) 여부를 검사 하는 명령문 내 에서처럼 보이므로 명령문의 각 분기 내 변수 상태는 if
다를 수 있습니다. else
블록 내 에서 두 코드에서 상태는 “널 (null)이 아닙니다”입니다. 특히, M3에서 상태는 “아마도 null”에서 “null이 아님”으로 변경됩니다.
지역 변수를 사용한 조건부 반환
이제 그 조건을 지역 변수에 끌어 올리십시오.
public static string M5(string? text)
{
bool isNull = text is null;
if (isNull)
{
return "";
}
else
{
return text; // Warning
}
}
public static string M6(string text)
{
bool isNull = text is null;
if (isNull)
{
return "";
}
else
{
return text; // Warning
}
}
M5와 M6 모두 경고를 발행합니다. 따라서 M5에서 상태 변경이 “아마도 null”에서 “null이 아님”으로 긍정적 인 효과를 얻지 못할뿐만 아니라 (M3에서 와 같이) 상태가 “에서 진행 M6에 영향을 not null “에서”null “일 수 있습니다. 정말 놀랐습니다.
그래서 우리는 그것을 배운 것 같습니다 :
- “로컬 변수 계산 방법”에 대한 논리는 상태 정보를 전파하는 데 사용되지 않습니다. 나중에 더 자세히.
- 널 (null) 비교를 도입하면 컴파일러는 이전에 널이 아닌 것으로 생각 된 것이 결국 널일 수 있음을 경고 할 수 있습니다.
무시 된 비교 후 무조건 리턴
무조건 반환 전에 비교를 도입하여 해당 글 머리 기호의 두 번째 요점을 살펴 보겠습니다. (따라서 비교 결과를 완전히 무시하고 있습니다.) :
public static string M7(string? text)
{
bool ignored = text is null;
return text; // Warning
}
public static string M8(string text)
{
bool ignored = text is null;
return text; // Warning
}
M8이 M2와 동등해야한다고 느낀다는 점에 주목하십시오. 둘 다 무조건 반환하는 null이 아닌 매개 변수가 있지만 null과 비교하면 상태가 “null이 아님”에서 “null이 될 수 있음”으로 변경됩니다. text
조건 전에 역 참조 를 시도하여 이에 대한 추가 증거를 얻을 수 있습니다 .
public static string M9(string text)
{
int length1 = text.Length; // No warning
bool ignored = text is null;
int length2 = text.Length; // Warning
return text; // No warning
}
return
명령문에 지금 경고가 표시되지 않는 방법에 주목하십시오 . 실행 후 상태 text.Length
는 “널이 아님”입니다 (해당 표현식을 성공적으로 실행하면 널이 될 수 없기 때문에). 따라서 text
매개 변수는 유형으로 인해 “널이 아님”으로 시작하고, 널 비교로 인해 “아마도 널”이 된 다음에 “널이 아님”이됩니다 text2.Length
.
어떤 비교가 상태에 영향을 줍니까?
그래야의 비교입니다 text is null
… 유사한 비교가 무슨 효과는? 널이 아닌 문자열 매개 변수로 시작하는 네 가지 방법이 더 있습니다.
public static string M10(string text)
{
bool ignored = text == null;
return text; // Warning
}
public static string M11(string text)
{
bool ignored = text is object;
return text; // No warning
}
public static string M12(string text)
{
bool ignored = text is { };
return text; // No warning
}
public static string M13(string text)
{
bool ignored = text != null;
return text; // Warning
}
그래서 비록 x is object
지금에 권장 대안 x != null
들이 같은 효과가 없습니다 : 만 비교 널 (null)와 (어떤과를 is
, ==
또는 !=
) “어쩌면 널 (null)”에 “NOT NULL”에서 상태를 변경합니다.
상태를 올리는 것이 왜 효과가 있습니까?
첫 번째 글 머리 기호로 돌아가서 왜 M5와 M6이 지역 변수로 이어지는 조건을 고려하지 않습니까? 다른 사람들을 놀라게하는 것만 큼 놀랍지는 않습니다. 이러한 종류의 논리를 컴파일러와 사양에 구축하는 것은 많은 작업이며 상대적으로 이익이 적습니다. 다음은 무언가 인라인이 영향을 미치는 nullable과 관련이없는 또 다른 예입니다.
public static int X1()
{
if (true)
{
return 1;
}
}
public static int X2()
{
bool alwaysTrue = true;
if (alwaysTrue)
{
return 1;
}
// Error: not all code paths return a value
}
우리alwaysTrue
는 항상 사실이라는 것을 알고 있지만 명세서 뒤의 코드를 if
도달 할 수 없게 만드는 사양의 요구 사항을 충족시키지 못합니다 .
명확한 할당에 관한 또 다른 예는 다음과 같습니다.
public static void X3()
{
string x;
bool condition = DateTime.UtcNow.Year == 2020;
if (condition)
{
x = "It's 2020.";
}
if (!condition)
{
x = "It's not 2020.";
}
// Error: x is not definitely assigned
Console.WriteLine(x);
}
우리 는 코드가 그 if
문장 중 하나에 정확하게 들어갈 것이라는 것을 알고 있지만 , 그 사양을 해결하는 데는 아무것도 없습니다. 정적 분석 도구는 그렇게 할 수는 있지만 언어 사양에 넣는 것은 나쁜 생각이 될 것입니다. IMO-정적 분석 도구는 시간이 지남에 따라 진화 할 수있는 모든 종류의 휴리스틱을 갖는 것이 좋습니다. 언어 사양.
답변
로컬 변수로 인코딩 된 의미를 추적 할 때이 경고를 생성하는 프로그램 흐름 알고리즘이 비교적 정교하지 않다는 증거를 발견했습니다.
흐름 검사기의 구현에 대한 구체적인 지식은 없지만 과거에 비슷한 코드의 구현에 대해 연구 한 결과 교육받은 추측을 할 수 있습니다. 플로우 체커는 오탐 (false positive) 경우 두 가지를 추론 할 가능성_test
이 있습니다 . (1) null 일 수 있습니다. 그렇지 않은 경우 처음에는 비교할 isNull
수 없기 때문에 (2) true 또는 false 일 수 있습니다. 그것을 할 수 없다면, 당신은 그것을 가질 수 없습니다 if
. 그러나 null이 아닌 return _test;
경우에만 실행 _test
되는 연결은 연결되지 않습니다.
이것은 놀랍게도 까다로운 문제이며, 전문가가 여러 해 동안 작업해온 도구를 정교하게 만드는 데 컴파일러가 시간이 걸릴 것으로 예상해야합니다. 예를 들어 Coverity flow checker는 두 변형 중 어느 것도 null을 반환하지 않는다고 추론하는 데 전혀 문제가 없지만 Coverity flow checker는 회사 고객에게 심각한 비용이 듭니다.
또한 Coverity checker는 밤새 큰 코드베이스에서 실행되도록 설계되었습니다 . C # 컴파일러의 분석은 편집기의 키 입력 사이에서 실행해야하므로 합리적으로 수행 할 수있는 심층 분석의 종류가 크게 변경됩니다.
답변
다른 모든 대답은 거의 정확합니다.
궁금한 사람이 있으면 https://github.com/dotnet/roslyn/issues/36927#issuecomment-508595947 에서 가능한 한 명시 적으로 컴파일러의 논리를 철자하려고했습니다.
언급되지 않은 한 가지는 널 검사가 “순수한”것으로 간주되어야하는지 여부를 결정하는 방법입니다.이를 수행하는 경우 널이 가능한지 심각하게 고려해야합니다. C #에는 많은 “부수적 인”null 검사가 있는데, 여기서 다른 것을 수행하는 과정에서 null을 테스트하므로 검사 세트를 사람들이 의도적으로 수행하고있는 것으로 확인하기로 결정했습니다. 우리가 함께했다 경험적의 이유 그래서 “단어 널 (null)이 들어”했다 x != null
와 x is object
다른 결과를 생성합니다.